/* 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>

   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 "katedocument.moc"
#include "kateglobal.h"
#include "katedialogs.h"
#include "katehighlight.h"
#include "kateview.h"
#include "kateautoindent.h"
#include "katetextline.h"
#include "katedocumenthelpers.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 "katetemplatehandler.h"
#include "katebuffer.h"
#include "kateundomanager.h"
#include "katepartpluginmanager.h"
#include "katevireplacemode.h"
#include "spellcheck/prefixstore.h"
#include "spellcheck/ontheflycheck.h"
#include "spellcheck/spellcheck.h"
#include "katescriptmanager.h"
#include "kateswapfile.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 <kcodecs.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>
//END  includes


static int dummy = 0;

class KateTemplateScript;

class KateDocument::LoadSaveFilterCheckPlugins
{
  public:
    LoadSaveFilterCheckPlugins() {
      KService::List traderList = KServiceTypeTrader::self()->query("KTextEditor/LoadSaveFilterCheckPlugin");

      foreach(const KService::Ptr &ptr, traderList)
      {
        QString libname;
        libname=ptr->library();
        libname=libname.right(libname.length()-12); //ktexteditor_ == 12
        m_plugins[libname]=0;//new KatePythonEncodingCheck();
        m_plugins2Service[libname] = ptr;
      }

    }
    ~LoadSaveFilterCheckPlugins() {
      if ( m_plugins.count()==0) return;
      QHashIterator<QString,KTextEditor::LoadSaveFilterCheckPlugin*>i(m_plugins);
        while (i.hasNext())
          i.next();
          delete i.value();
      m_plugins.clear();
    }
    bool preSavePostDialogFilterCheck(const QString& pluginName,KateDocument *document,QWidget *parentWidget) {
      KTextEditor::LoadSaveFilterCheckPlugin *plug=getPlugin(pluginName);
      if (!plug) {
        if (KMessageBox::warningContinueCancel (parentWidget
        , i18n ("The filter/check plugin '%1' could not be found, still continue saving of %2", pluginName,document->url().pathOrUrl())
        , i18n ("Saving problems")
        , KGuiItem(i18n("Save Nevertheless"))
        , KStandardGuiItem::cancel()) != KMessageBox::Continue)
          return false;
        else
          return true;
      }
      return plug->preSavePostDialogFilterCheck(document);
    }
    void postLoadFilter(const QString& pluginName,KateDocument *document) {
      KTextEditor::LoadSaveFilterCheckPlugin *plug=getPlugin(pluginName);
      if (!plug) return;
      plug->postLoadFilter(document);
    }
    bool postSaveFilterCheck(const QString& pluginName,KateDocument *document,bool saveas) {
      KTextEditor::LoadSaveFilterCheckPlugin *plug=getPlugin(pluginName);
      if (!plug) return false;
      return plug->postSaveFilterCheck(document,saveas);
    }
  private:
    KTextEditor::LoadSaveFilterCheckPlugin *getPlugin(const QString & pluginName)
    {
      if (!m_plugins.contains(pluginName)) return 0;
      if (!m_plugins.value(pluginName, 0)) {
        m_plugins[pluginName]=m_plugins2Service.value(pluginName)->createInstance<KTextEditor::LoadSaveFilterCheckPlugin>();
      }
      return m_plugins.value(pluginName);
    }
    QHash <QString,KTextEditor::LoadSaveFilterCheckPlugin*> m_plugins;
    QHash <QString, KService::Ptr> m_plugins2Service;
};

//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_undoManager(new KateUndoManager(this)),
  m_editableMarks(markType01),
  m_annotationModel(0),
  m_saveAs(false),
  m_isasking(0),
  m_blockRemoveTrailingSpaces(false),
  m_buffer(new KateBuffer(this)),
  m_indenter(new KateAutoIndent(this)),
  hlSetByUser(false),
  m_bomSetByUser(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_savingToUrl(false),
  m_onTheFlyChecker(0)
{
  setComponentData ( KateGlobal::self()->componentData () );

  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)));
  connect(m_buffer, SIGNAL(respellCheckBlock(int,int)), this , SLOT(respellCheckBlock(int,int)));
  connect(m_buffer, SIGNAL(codeFoldingUpdated()),this,SIGNAL(codeFoldingUpdated()));
  connect(this,SIGNAL(aboutToReload(KTextEditor::Document*)),foldingTree(),SLOT(saveFoldingState()));
  connect(this,SIGNAL(reloaded(KTextEditor::Document*)),foldingTree(),SLOT(applyFoldingState()));

  // 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)) );

  connect (this,SIGNAL(completed()),this,SLOT(slotCompleted()));
  connect (this,SIGNAL(canceled(QString)),this,SLOT(slotCanceled()));
  // update doc name
  setDocName (QString());

  // 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);

  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 = const_cast<KateDocument*>(this)->toVirtualColumn(range.start());
  int col2 = const_cast<KateDocument*>(this)->toVirtualColumn(range.end());

  Kate::TextLine tl = const_cast<KateDocument*>(this)->kateTextLine(line);
  col1 = tl->fromVirtualColumn(col1, config()->tabWidth());
  col2 = tl->fromVirtualColumn(col2, config()->tabWidth());

  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() << k_funcinfo << "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();

  if (position.column() >= 0 && position.column() < textLine->string().length())
    return textLine->string().at(position.column());

  return QChar();
}

QStringList KateDocument::textLines( const KTextEditor::Range & range, bool blockwise ) const
{
  QStringList ret;

  if (!range.isValid()) {
    kWarning() << k_funcinfo << "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 )
{
  //m_buffer->foldingTree()->printTree();

  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 = kateTextLine( currentLine );
  if (l)
    insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );

  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;
      }
      else
      {
        if ( currentLine == lastLine() )
          editWrapLine(currentLine , insertColumn + pos - currentLineStart);
        insertColumn = position.column(); // tab expansion might change this
      }

      currentLine++;
      currentLineStart = pos + 1;
      l = kateTextLine( currentLine );
      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;
        l = kateTextLine( currentLine );
        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) {
      Kate::TextLine tl = const_cast<KateDocument*>(this)->kateTextLine(line);
      int col1 = tl->fromVirtualColumn(vc1, config()->tabWidth());
      int col2 = tl->fromVirtualColumn(vc2, config()->tabWidth());
      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::numVisLines() const
{
  return m_buffer->countVisible ();
}

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)
      return false;

    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;
      }

      int searchStart = qMin (z2, l->length()-1);

      // 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 = 0;
      int nw = 0; // alternative position, a non word character
      for (z=searchStart; z > 0; z--)
      {
        if (t.at(z).isSpace()) break;
        if ( ! nw && highlight()->canBreakAt( t.at(z) , l->attribute(z) ) )
        nw = z;
      }

      bool removeTrailingSpace = false;
      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++;
        removeTrailingSpace = true;
      }
      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 && nw < col ) nw++; // break on the right side of the character
        z = nw ? nw : col;
      }

      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++;
      }

      if (removeTrailingSpace) {
        // cu space
        editRemoveText (line, z - 1, 1);
      }
    }
  }

  editEnd ();

  return true;
}

bool KateDocument::editInsertText ( int line, int col, const QString &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 )
{
  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)));

  removeTrailingSpace( line );

  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 )
{
  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)
{
  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, (!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 )
{
  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->insertText (KTextEditor::Cursor (line, col), nextLine->string().left((nextLine->length() < length) ? nextLine->length() : length));
    m_buffer->removeText (KTextEditor::Range (KTextEditor::Cursor (line + 1, 0), KTextEditor::Cursor (line + 1, (nextLine->length() < length) ? nextLine->length() : length)));
  }

  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 )
{
  if (line < 0)
    return false;

  if (!isReadWrite())
    return false;

  if ( line > lines() )
    return false;

  editStart ();

  m_undoManager->slotLineInserted(line, s);

  removeTrailingSpace( line ); // old line

  // 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);

  removeTrailingSpace( line ); // new line

  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 )
{
  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--) {
    KateLineInfo info;
    lineInfo(&info, line);
    if (info.startsInVisibleBlock)
      foldingTree()->toggleRegionVisibility(line);
  }

  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

KateUndoManager* KateDocument::undoManager()
{
  return m_undoManager;
}

//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)
{
  m_buffer->setHighlight (KateHlManager::self()->nameFind(name));
  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()
{
  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
    updateFileType (kconfig.readEntry("Mode", "Normal"));
  }

  if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipHighlighting)) {
    // restore the hl stuff
    m_buffer->setHighlight(KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")));
  }

  if (!swapFile()->shouldRecover()) {
    // restore "read only mode" property, but don't force ReadWrite if a swap file exists to recover data
    // todo: what does m_bReadOnly mean?
    setReadWrite(kconfig.readEntry("ReadWrite", true));
  }

  // 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 );

  if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipFolding)) {
      // restore folding nodes
      foldingTree()->readSessionConfig(kconfig);
  }
}

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());
  }

  // read only mode
  kconfig.writeEntry("ReadWrite", isReadWrite());

  // 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 );

  if(!(configParameters & KTextEditor::ParameterizedSessionConfigInterface::SkipFolding)) {
    // save folding
    foldingTree()->writeSessionConfig(kconfig);
  }

}

//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 > 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 > 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 > 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 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 ();

  //
  // mime type magic to get encoding right
  //
  QString mimeType = arguments().mimeType();
  int pos = mimeType.indexOf(';');
  if (pos != -1)
    setEncoding (mimeType.mid(pos+1));

  // do we have success ?
  emit KTextEditor::Document::textRemoved(this, documentRange());
  emit KTextEditor::Document::textRemoved(this, documentRange(), m_buffer->text());

  bool success = m_buffer->openFile (localFilePath());

  // disable view updates
  foreach (KateView * view, m_views)
    view->setUpdatesEnabled (false);

  //
  // yeah, success
  //
  if (success)
  {
    // update file type
    updateFileType (KateGlobal::self()->modeManager()->fileType (this));

    // read dir config (if possible and wanted)
    readDirConfig ();

    // read vars
    readVariables();

    // remove trailing space
    // NOTE: wait until now because the config or variables might tell us not to do this!
    m_buffer->setRemoveTrailingSpaces (config()->removeSpaces());
    if ( m_buffer->removeTrailingSpaces() )
    {
      int n = lines();
      while (n--)
        removeTrailingSpace (n);
    }

    // update the md5 digest
    createDigest( m_digest );

    if (!m_postLoadFilterChecks.isEmpty())
    {
      LoadSaveFilterCheckPlugins *lscps=loadSaveFilterCheckPlugins();
      foreach(const QString& checkplugin, m_postLoadFilterChecks)
      {
         lscps->postLoadFilter(checkplugin,this);
      }
    }
  }

  //
  // 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
    //
    setDocName  (QString());
  }
  //
  // to houston, we are not modified
  //
  if (m_modOnHd)
  {
    m_modOnHd = false;
    m_modOnHdReason = OnDiskUnmodified;
    emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
  }

  //
  // display errors
  //
  QWidget *parentWidget(dialogParent());

  if (!suppressOpeningErrorDialogs())
  {
    if (!success)
      KMessageBox::error (parentWidget, 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()));
  }

  if (!success) {
    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()));
  }

  // warn: broken encoding
  if (m_buffer->brokenEncoding())
  {
    // this file can't be saved again without killing it
    setReadWrite( false );

    if (!suppressOpeningErrorDialogs())
      KMessageBox::information (parentWidget
        , 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 ()))
        , i18n ("Broken Encoding")
        , "Broken Encoding Warning");
    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 );

    if (!suppressOpeningErrorDialogs())
      KMessageBox::information (parentWidget
        , i18n ("The file %1 was opened and contained too long lines (more than %2 characters)."
                " Too long lines were wrapped and the document is set to read-only mode, as saving will modify its content.", this->url().pathOrUrl(),config()->lineLengthLimit())
        , i18n ("Too Long Lines Wrapped")
        , "Too Long Lines Wrapped Warning");
    setOpeningError(true);
    setOpeningErrorMessage(i18n ("The file %1 was opened and contained too long lines (more than %2 characters)."
                " Too long 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());

  //
  // warn -> try to save binary file!!!!!!!
  //
#if 0
  if (m_buffer->binary() && (KMessageBox::warningContinueCancel (parentWidget
        , i18n ("The file %1 is a binary, saving it will result in a corrupt file.", url().pathOrUrl())
        , i18n ("Trying to Save Binary File")
        , KGuiItem(i18n("Save Nevertheless"))
        , KStandardGuiItem::cancel(), "Binary File Save Warning") != KMessageBox::Continue))
    return false;
#endif

  // 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() );
    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
    {
      QWidget *w = widget ();
      if (!w && !m_views.isEmpty ())
        w = m_views.first();

      // get the right permissions, start with safe default
      mode_t  perms = 0600;
      KIO::UDSEntry fentry;
      if (KIO::NetAccess::stat (url(), fentry, QApplication::activeWindow()))
      {
        kDebug( 13020 ) << "stating succesfull: " << url();
        KFileItem item (fentry, url());
        perms = item.permissions();

        // do a evil copy which will overwrite target if possible
        KIO::FileCopyJob *job = KIO::file_copy ( url(), u, -1, KIO::Overwrite );
        backupSuccess = KIO::NetAccess::synchronousRun(job, w);
      }
      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
  updateFileType (KateGlobal::self()->modeManager()->fileType (this));

  if (!m_preSavePostDialogFilterChecks.isEmpty())
  {
    LoadSaveFilterCheckPlugins *lscps=loadSaveFilterCheckPlugins();
    foreach(const QString& checkplugin, m_preSavePostDialogFilterChecks)
    {
       if (lscps->preSavePostDialogFilterCheck(checkplugin,this,parentWidget)==false)
         return false;
    }
  }

  // remember the oldpath...
  QString oldPath = m_dirWatchFile;

  // remove file from dirwatch
  deactivateDirWatch ();

  //
  // 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 md5 digest
  createDigest( m_digest );

  // add m_file again to dirwatch
  activateDirWatch ();

  // update file type
//  updateFileType (KateGlobal::self()->modeManager()->fileType (this));

  // read dir config (if possible and wanted)
  if ( url().isLocalFile())
  {
    QFileInfo fo (oldPath), fn (m_dirWatchFile);

    if (fo.path() != fn.path())
      readDirConfig();
  }

  // read our vars
  readVariables();

  //
  // we are not modified
  //
  if (m_modOnHd)
  {
    m_modOnHd = false;
    m_modOnHdReason = OnDiskUnmodified;
    emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
  }

  // update document name...
  setDocName( QString() );

  // url may have changed...
  emit documentUrlChanged (this);

  m_savingToUrl=true;

  // (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();

  // 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::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))
        return false;
    }
  }

  //
  // first call the normal kparts implementation
  //
  if (!KParts::ReadWritePart::closeUrl ())
    return false;

  // Tell the world that we're about to go ahead with the close
  if (!m_reloading)
    emit aboutToClose(this);

  /**
   * 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
    setDocName (QString());
  }

  // 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 ();
    }
  }
}

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( (KateView *) view );
  m_textEditViews.removeAll( view  );
}

void KateDocument::setActiveView(KTextEditor::View* view)
{
  if ( m_activeView == view )
    return;

  m_activeView = (KateView*)view;
}

bool KateDocument::ownedView(KateView *view) {
  // do we own the given view?
  return (m_views.contains(view));
}

uint KateDocument::toVirtualColumn( const KTextEditor::Cursor& cursor )
{
  Kate::TextLine textLine = m_buffer->plainLine(cursor.line());

  if (textLine)
    return textLine->toVirtualColumn(cursor.column(), config()->tabWidth());
  else
    return 0;
}

bool KateDocument::typeChars ( KateView *view, const QString &chars )
{
  Kate::TextLine textLine = m_buffer->plainLine(view->cursorPosition().line ());

  if (!textLine)
    return false;

  bool bracketInserted = false;
  QString buf;
  foreach(const QChar& ch, chars)
  {
    if (ch.isPrint() || ch == QChar::fromAscii('\t'))
    {
      buf.append (ch);

      if (!bracketInserted && (config()->autoBrackets()))
      {
        QChar end_ch;

        if (ch == '(') { end_ch = ')'; }
        if (ch == '[') { end_ch = ']'; }
        if (ch == '{') { end_ch = '}'; }
        if (ch == '"') { end_ch = '"'; }
        if (ch == '\'') { end_ch = '\''; }

        if (!end_ch.isNull()) {
          bracketInserted = true;

          if (view->selection()) {
            buf.append(view->selectionText());
          }

          buf.append(end_ch);
        }
      }
    }
  }

  if (buf.isEmpty())
    return false;

  editStart ();

  if (!view->config()->persistentSelection() && view->selection() )
    view->removeSelectedText();

  KTextEditor::Cursor oldCur (view->cursorPosition());

  if (config()->ovr()
      || (view->viInputMode() && view->getViInputModeManager()->getCurrentViMode() == ReplaceMode)) {

    KTextEditor::Range r = KTextEditor::Range(view->cursorPosition(), qMin(buf.length(),
          textLine->length() - view->cursorPosition().column()));

    // replace mode needs to know what was removed so it can be restored with backspace
    if (view->viInputMode() && view->getViInputModeManager()->getCurrentViMode() == ReplaceMode
            && oldCur.column() < line( view->cursorPosition().line() ).length() ) {
      QChar removed = line( view->cursorPosition().line() ).at( r.start().column() );
      view->getViInputModeManager()->getViReplaceMode()->overwrittenChar( removed );
    }

    removeText(r);
  }

  insertText(view->cursorPosition(), buf);
  if (bracketInserted)
    view->setCursorPositionInternal (view->cursorPosition() - KTextEditor::Cursor(0,1));

  KTextEditor::Cursor b(view->cursorPosition());
  m_indenter->userTypedChar (view, b, chars.isEmpty() ? QChar() :  chars.at(chars.length() - 1));

  editEnd ();

  view->slotTextInserted (view, oldCur, chars);
  return true;
}

void KateDocument::newLine( KateView *v )
{
  editStart();

  if( !v->config()->persistentSelection() && v->selection() )
    v->removeSelectedText();

  // 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());

  // second: indent the new line, if needed...
  m_indenter->userTypedChar(v, v->cursorPosition(), '\n');

  removeTrailingSpace( ln );

  editEnd();
}

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() ) {
    view->removeSelectedText();
    return;
  }

  uint col = qMax( c.column(), 0 );
  uint line = qMax( c.line(), 0 );

  if ((col == 0) && (line == 0))
    return;

  int complement = 0;
  if (col > 0)
  {
    if (config()->autoBrackets())
    {
      // if inside empty (), {}, [], '', "" delete both
      Kate::TextLine tl = m_buffer->plainLine(line);
      if(!tl) return;
      QChar prevChar = tl->at(col-1);
      QChar nextChar = tl->at(col);

      if ( (prevChar == '"' && nextChar == '"') ||
           (prevChar == '\'' && nextChar == '\'') ||
           (prevChar == '(' && nextChar == ')') ||
           (prevChar == '[' && nextChar == ']') ||
           (prevChar == '{' && nextChar == '}') )
      {
        complement = 1;
      }
    }
    if (!(config()->backspaceIndents()))
    {
      // ordinary backspace
      //c.cursor.col--;
      removeText(KTextEditor::Range(line, col-1, line, col+complement));
    }
    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+complement));
    }
  }
  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() ) {
    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, QClipboard::Mode mode )
{
  QString s = QApplication::clipboard()->text(mode);

  if (s.isEmpty())
    return;

  int lines = s.count (QChar::fromAscii ('\n'));

  m_undoManager->undoSafePoint();

  editStart ();
  blockRemoveTrailingSpaces(true); // bug #242723, see unit test bug242723_test

  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();
    view->removeSelectedText();
  }

  if (config()->ovr()) {
    QStringList pasteLines = s.split(QLatin1Char('\n'));

    if (!view->blockSelectionMode()) {
      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->blockSelectionMode());
  blockRemoveTrailingSpaces(false);

  for (int i = pos.line(); i < pos.line() + lines; ++i)
    removeTrailingSpace(i);

  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->blockSelectionMode())
    view->setCursorPositionInternal(pos);

  if (config()->indentPastedText())
  {
    KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0),
                                                  KTextEditor::Cursor(pos.line() + lines, 0));

    int start = view->selectionRange().start().line();
    const int end = view->selectionRange().end().line();

    editStart();

    blockRemoveTrailingSpaces(true);
    m_indenter->indent(view, range);
    blockRemoveTrailingSpaces(false);

    for (; start <= end; ++start)
      removeTrailingSpace(start);

    editEnd();
  }

  if (!view->blockSelectionMode())
    emit charactersSemiInteractivelyInserted (pos, s);
  m_undoManager->undoSafePoint();
}

void KateDocument::indent (KTextEditor::Range range, int change)
{
  // dominik: if there is a selection, iterate afterwards over all lines and
  // remove trailing spaces
  int start = range.start().line();
  const int end = range.end().line();

  editStart();
  blockRemoveTrailingSpaces(true);
  m_indenter->changeIndent(range, change);
  blockRemoveTrailingSpaces(false);

  if (range.numberOfLines() > 1) {
    for (; start <= end; ++start)
      removeTrailingSpace(start);
  }
  editEnd();
}

void KateDocument::align(KateView *view, const KTextEditor::Range &range)
{
  editStart();

  blockRemoveTrailingSpaces(true);
  m_indenter->indent(view, range);
  blockRemoveTrailingSpaces(false);

  for (int start = range.start().line(); start <= range.end().line(); ++start) {
    removeTrailingSpace(start);
  }

  editEnd();
}

void KateDocument::insertTab( KateView *, const KTextEditor::Cursor& c )
{
  if (!isReadWrite())
    return;

  editStart();
  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 l = line;
  int c = 0;

  if ( hassel )
  {
    l = v->selectionRange().start().line();
    c = v->selectionRange().start().column();
  }

  int startAttrib = 0;
  Kate::TextLine ln = kateTextLine( line );

  if ( c < ln->length() )
    startAttrib = ln->attribute( c );
  else if ( !ln->ctxArray().isEmpty() )
    startAttrib = highlight()->attribute( ln->ctxArray().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 ) );
      if ((!removed) && foldingTree()) {
        kDebug(13020)<<"easy approach for uncommenting did not work, trying harder (folding tree)";
        int commentRegion=(highlight()->commentRegion(startAttrib));
        if (commentRegion){
           KateCodeFoldingNode *n=foldingTree()->findNodeForPosition(line,column);
           if (n) {
            KTextEditor::Cursor start,end;
            if ((n->nodeType()==(int)commentRegion) && n->getBegin(foldingTree(), &start) && n->getEnd(foldingTree(), &end)) {
                kDebug(13020)<<"Enclosing region found:"<<start.column()<<"/"<<start.line()<<"-"<<end.column()<<"/"<<end.line();
                removed = removeStartStopCommentFromRegion(start,end,startAttrib);
             } else {
                  kDebug(13020)<<"Enclosing region found, but not valid";
                  kDebug(13020)<<"Region found: "<<n->nodeType()<<" region needed: "<<commentRegion;
             }
            //perhaps nested regions should be hadled here too...
          } else kDebug(13020)<<"No enclosing region found";
        } else kDebug(13020)<<"No comment region specified for current hl";
      }
    }
    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->blockSelectionMode())
        start = selection.start().column();

      if (range.start().line() == selection.end().line() || v->blockSelectionMode())
        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->blockSelectionMode() ) &&
                   ! 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::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end)
{
  // May need to switch start/end cols if in block selection mode
/*  if (blockSelectionMode() && start.column() > end.column()) {
    int sc = start.column();
    start.setColumn(end.column());
    end.setColumn(sc);
  }
*/
  foreach (KateView* view, m_views)
    view->tagLines(start, end, true);
}

void KateDocument::repaintViews(bool paintOnlyDirty)
{
  foreach(KateView *view,m_views)
    view->repaintText(paintOnlyDirty);
}

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 ); }

/*
   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();
  QScopedPointer<KTextEditor::MovingCursor> cursor(newMovingCursor(range.start()));
  int validAttr = kateTextLine(cursor->line())->attribute(cursor->column());

  while( cursor->line() >= minLine && cursor->line() <= maxLine ) {

    if (!cursor->move(searchDir))
      return false;

    if (kateTextLine(cursor->line())->attribute(cursor->column()) == validAttr )
    {
      /* Check for match */
      QChar c = character(cursor->toCursor());
      if( c == bracket ) {
        nesting++;
      } else if( c == opposite ) {
        if( nesting == 0 ) {
          if (searchDir > 0) // forward
            range.end() = cursor->toCursor();
          else
            range.start() = cursor->toCursor();
          return true;
        }
        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::setDocName (const QString &_name )
{
  const QString name = removeNewLines(_name);
  /**
   * avoid senseless name changes
   */
  if ( name == m_docName )
    return;

  /**
   * if name given, use it!
   */
  if ( !name.isEmpty() ) {
    m_docName = name;
    emit documentNameChanged (this);
    return;
  }

  // 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());

  if (m_docName.isEmpty())
    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")), KGuiItem(i18n("&Ignore Changes")));

      if ( i != KMessageBox::Yes)
      {
        if (i == KMessageBox::No)
        {
          m_modOnHd = false;
          m_modOnHdReason = OnDiskUnmodified;
          emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
        }

        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() );
    m_reloading = false;

    // restore cursor positions for all views
    QLinkedList<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 ();
}

void KateDocument::dumpRegionTree()
{
  m_buffer->foldingTree()->debugDump();
}
//END

void KateDocument::lineInfo (KateLineInfo *info, int line) const
{
  m_buffer->lineInfo(info,line);
}

KateCodeFoldingTree *KateDocument::foldingTree ()
{
  return m_buffer->foldingTree();
}

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!
  KateView *v;
  foreach (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 (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 == "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 ) )
        m_config->setRemoveTrailingDyn( state );
      else if ( var == "wrap-cursor" && checkBoolValue( val, &state ) )
        m_config->setWrapCursor( state );
      else if ( var == "auto-brackets" && checkBoolValue( val, &state ) )
        m_config->setAutoBrackets( state );
      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 == "replace-trailing-space-save" && checkBoolValue( val, &state ) )
        m_config->setRemoveSpaces( 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 == "presave-postdialog")
        setPreSavePostDialogFilterChecks(val.split(','));
      else if (var == "postsave")
        setPostSaveFilterChecks(val.split(','));
      else if (var == "postload")
        setPostLoadFilterChecks(val.split(','));
      else if ( var == "syntax" || var == "hl" )
      {
        setHighlightingMode( val );
      }
      else if ( var == "mode" )
      {
        setMode( 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 )
{
  KateView *v;
  bool state;
  int n;
  QColor c;
  foreach (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->setBlockSelectionMode( 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 ); // FIXME uint, > 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 md5 with the one we have (if we have one)
    if ( ! m_digest.isEmpty() )
    {
      QByteArray tmp;
      if ( createDigest( tmp ) && tmp == m_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);
  }
}

bool KateDocument::createDigest( QByteArray &result )
{
  bool ret = false;
  result.clear();
  if ( url().isLocalFile() )
  {
    QFile f ( url().toLocalFile() );
    if ( f.open( QIODevice::ReadOnly) )
    {
      KMD5 md5;
      ret = md5.update( f );
      md5.hexDigest( result );
      f.close();
      ret = true;
    }
  }
  return ret;
}

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::removeTrailingSpace(int line)
{
  // if undo/redo is active, never remove trailing spaces, because the undo/redo
  // action also sets the cursor position. If the trailing spaces are removed,
  // the cursor position can get invalid (i.e. it is behind the last column).
  // Then, moving the cursor leads to a crash, see bug #152203.
  if (!m_undoManager->isActive())
    return;

  // remove trailing spaces from left line if required
  if (m_blockRemoveTrailingSpaces
      || !(config()->removeTrailingDyn()))
    return;

  Kate::TextLine ln = plainKateTextLine(line);

  if (!ln || ln->length() == 0)
    return;

  if (activeView() && line == activeView()->cursorPosition().line()
      && activeView()->cursorPosition().column() >= qMax(0, ln->lastChar()))
    return;

  const int p = ln->lastChar() + 1;
  const int l = ln->length() - p;
  if (l > 0) {
    m_blockRemoveTrailingSpaces = true;
    editRemoveText(line, p, l);
    m_blockRemoveTrailingSpaces = false;
  }
}

void KateDocument::updateFileType (const QString &newType, bool user)
{
  if (user || !m_fileTypeSetByUser)
  {
    if (!newType.isEmpty())
    {
          m_fileType = newType;

          m_config->configStart();

          if (!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...
           */
          if (!KateGlobal::self()->modeManager()->fileType(newType).indenter.isEmpty())
             config()->setIndentationMode (KateGlobal::self()->modeManager()->fileType(newType).indenter);

          // views!
          KateView *v;
          foreach (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 (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() << "auto-brackets";
}

QVariant KateDocument::configValue(const QString &key)
{
  if (key == "auto-brackets") {
    return m_config->autoBrackets();
  } else 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();
  }

  // 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 == "auto-brackets") {
      m_config->setAutoBrackets(bValue);
    } else 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);
    }
  }
}

//END KTextEditor::ConfigInterface

//BEGIN KTextEditor::TemplateInterface
bool KateDocument::insertTemplateTextImplementation( const KTextEditor::Cursor &c,
                                                     const QString &templateString,
                                                     const QMap<QString,QString> &initialValues,
                                                     KTextEditor::TemplateScript* templateScript,
                                                     KateView* view)
{
  if (templateString.isEmpty()) return false;

  KateTemplateScript* kateTemplateScript =
    KateGlobal::self()->scriptManager()->templateScript(templateScript);

  // the handler will delete itself when necessary
  new KateTemplateHandler(view, c, templateString, initialValues, m_undoManager, kateTemplateScript);

  return true;
}
//END KTextEditor::TemplateInterface


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();
}

uint KateDocument::getRealLine( unsigned int virtualLine )
{
  return m_buffer->lineNumber (virtualLine);
}

uint KateDocument::getVirtualLine( unsigned int realLine )
{
  return m_buffer->lineVisibleNumber (realLine);
}

uint KateDocument::visibleLines( )
{
  return m_buffer->countVisible ();
}

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)
{
  m_undoManager->undoSafePoint();
  m_undoManager->setAllowComplexMerge(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 ();
}

KateDocument::LoadSaveFilterCheckPlugins* KateDocument::loadSaveFilterCheckPlugins()
{
  K_GLOBAL_STATIC(KateDocument::LoadSaveFilterCheckPlugins, s_loadSaveFilterCheckPlugins)
  return s_loadSaveFilterCheckPlugins;
}

//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::slotCanceled() {
  m_savingToUrl=false;
  m_saveAs=false;
}

void KateDocument::slotCompleted() {
  if (m_savingToUrl) {
    if (!m_postSaveFilterChecks.isEmpty())
    {
      LoadSaveFilterCheckPlugins *lscps1=loadSaveFilterCheckPlugins();
      foreach(const QString& checkplugin, m_postSaveFilterChecks)
      {
        if (lscps1->postSaveFilterCheck(checkplugin,this,m_saveAs)==false)
          break;
      }
    }
  }
  emit documentSavedOrUploaded(this,m_saveAs);
  m_savingToUrl=false;
  m_saveAs=false;
}

bool KateDocument::save() {
  m_saveAs = false;
  return KTextEditor::Document::save();
}

bool KateDocument::saveAs( const KUrl &url ) {
  m_saveAs = true;
  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))
    {
      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, 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)
  {
    if (!m_onTheFlyChecker) {
      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<int> & intAttrs = kateLine->attributesList();

  Q_ASSERT(intAttrs.size() % 3 == 0);

  for ( int i = 0; i < intAttrs.size(); i += 3 ) {
    attribs << KTextEditor::HighlightInterface::AttributeBlock(
      intAttrs.at(i),
      intAttrs.at(i+1),
      view->renderer()->attribute(intAttrs.at(i+2))
    );
  }

  return attribs;
}

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 QVector< short >& ctxs = kateLine->ctxArray();
    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;
}

int KateDocument::defStyleNum(int line, int column)
{
  QList<KTextEditor::Attribute::Ptr> attributes = highlight()->attributes(((KateView*) activeView())->renderer()->config()->schema());
  KTextEditor::Attribute::Ptr a = attributes[plainKateTextLine(line)->attribute(column)];
  return a->property(KateExtendedAttribute::AttributeDefaultStyleIndex).toInt();
}

bool KateDocument::isComment(int line, int column)
{
  const int defaultStyle = defStyleNum(line, column);
  return defaultStyle == KTextEditor::HighlightInterface::dsComment;
}

// kate: space-indent on; indent-width 2; replace-tabs on;
