• Skip to content
  • Skip to link menu
KDE 4.1 API Reference
  • KDE API Reference
  • kdelibs
  • Sitemap
  • Contact Us
 

Kate

katedocument.cpp

Go to the documentation of this file.
00001 /* This file is part of the KDE libraries
00002    Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org>
00003    Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org>
00004    Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de>
00005    Copyright (C) 2006 Hamish Rodda <rodda@kde.org>
00006    Copyright (C) 2007 Mirko Stocker <me@misto.ch>
00007 
00008    This library is free software; you can redistribute it and/or
00009    modify it under the terms of the GNU Library General Public
00010    License version 2 as published by the Free Software Foundation.
00011 
00012    This library is distributed in the hope that it will be useful,
00013    but WITHOUT ANY WARRANTY; without even the implied warranty of
00014    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
00015    Library General Public License for more details.
00016 
00017    You should have received a copy of the GNU Library General Public License
00018    along with this library; see the file COPYING.LIB.  If not, write to
00019    the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
00020    Boston, MA 02111-13020, USA.
00021 */
00022 
00023 //BEGIN includes
00024 #include "katedocument.h"
00025 #include "katedocument.moc"
00026 #include "katekeyinterceptorfunctor.h"
00027 #include "kateglobal.h"
00028 #include "katedialogs.h"
00029 #include "katehighlight.h"
00030 #include "kateview.h"
00031 #include "kateautoindent.h"
00032 #include "katetextline.h"
00033 #include "katedocumenthelpers.h"
00034 #include "kateprinter.h"
00035 #include "katesmartcursor.h"
00036 #include "katerenderer.h"
00037 #include <ktexteditor/attribute.h>
00038 #include "kateconfig.h"
00039 #include "katemodemanager.h"
00040 #include "kateschema.h"
00041 #include "katetemplatehandler.h"
00042 #include "katesmartmanager.h"
00043 #include <ktexteditor/plugin.h>
00044 #include "kateedit.h"
00045 #include "katebuffer.h"
00046 #include "kateundo.h"
00047 #include "katepartpluginmanager.h"
00048 
00049 #include <kio/job.h>
00050 #include <kio/jobuidelegate.h>
00051 #include <kio/netaccess.h>
00052 #include <kio/kfileitem.h>
00053 
00054 #include <kparts/event.h>
00055 
00056 #include <klocale.h>
00057 #include <kglobal.h>
00058 #include <kapplication.h>
00059 #include <kmenu.h>
00060 #include <kconfig.h>
00061 #include <kfiledialog.h>
00062 #include <kmessagebox.h>
00063 #include <kstandardaction.h>
00064 #include <kxmlguifactory.h>
00065 #include <kdebug.h>
00066 #include <kglobalsettings.h>
00067 #include <klibloader.h>
00068 #include <kdirwatch.h>
00069 #include <kencodingfiledialog.h>
00070 #include <ktemporaryfile.h>
00071 #include <kcodecs.h>
00072 #include <kstandarddirs.h>
00073 
00074 #include <QtDBus/QtDBus>
00075 #include <QtCore/QTimer>
00076 #include <QtCore/QFile>
00077 #include <QtGui/QClipboard>
00078 #include <QtCore/QTextStream>
00079 #include <QtCore/QTextCodec>
00080 #include <QtCore/QMap>
00081 #include <QtCore/QMutex>
00082 //END  includes
00083 
00084 
00085 
00086 // Turn debug messages on/off here
00087 // #define FAST_DEBUG_ENABLE
00088 
00089 #ifdef FAST_DEBUG_ENABLE
00090 # define FAST_DEBUG(x) (kDebug( 13020 ) << x)
00091 #else
00092 # define FAST_DEBUG(x)
00093 #endif
00094 
00095 
00096 
00097 //BEGIN PRIVATE CLASSES
00098 class KatePartPluginItem
00099 {
00100   public:
00101     KTextEditor::Plugin *plugin;
00102 };
00103 //END PRIVATE CLASSES
00104 
00105 static int dummy = 0;
00106 
00107 #ifdef __GNUC__
00108 #warning consider moving this to KTextEditor
00109 #endif
00110 class LoadSaveFilterCheckPlugin {
00111   public:
00112     LoadSaveFilterCheckPlugin() {}
00113     virtual ~LoadSaveFilterCheckPlugin(){}
00114     /*this one should be called once everything is set up for saving (especially the encoding has been determind (example: used for checking python source encoding headers))*/
00115     virtual bool preSavePostDialogFilterCheck(KTextEditor::Document *document) =0;
00116     /*this one should be called once the document has been completely loaded and configured (encoding,highlighting, ...))*/
00117     virtual void postLoadFilter(KTextEditor::Document *document) =0;
00118 };
00119 
00120 class KatePythonEncodingCheck: public LoadSaveFilterCheckPlugin {
00121   public:
00122     KatePythonEncodingCheck():LoadSaveFilterCheckPlugin(){interpreterLine=QRegExp(QString("#!.*$"));}
00123     virtual ~KatePythonEncodingCheck(){}
00124     virtual bool preSavePostDialogFilterCheck(KTextEditor::Document *document)
00125     {
00126       kDebug(13020)<<"KatePythonEncodingCheck::preSavePostDialogCheck";
00127       //QString codec=document->config()->codec()->name().toLower();
00128       QString codec=document->encoding().toLower();
00129       codec.replace(' ','-');
00130 //  "#\s*-\*\-\s*coding[:=]\s*([-\w.]+)\s*-\*-\s*$"
00131       bool firstIsInterpreter=false;
00132       QRegExp encoding_regex(QString("#\\s*-\\*\\-\\s*coding[:=]\\s*%1\\s*-\\*-\\s*$").arg(codec));
00133       bool correctencoding=false;
00134       if (document->lines()>0)
00135       {
00136         if (encoding_regex.exactMatch(document->line(0))) correctencoding=true;
00137         else if (document->lines()>1) {
00138           if (interpreterLine.exactMatch(document->line(0)))
00139           {
00140             firstIsInterpreter=true;
00141             if (encoding_regex.exactMatch(document->line(1))) correctencoding=true;
00142           }
00143         }
00144       }
00145       if (!correctencoding) {
00146         QString addLine(QString("# -*- coding: %1 -*-").arg(codec));
00147         int what=KMessageBox::warningYesNoCancel (document->activeView()
00148         , i18n ("You are trying to save a python file as non ASCII, without specifiying a correct source encoding line for encoding \"%1\"", codec)
00149         , i18n ("No encoding header")
00150         , KGuiItem(i18n("Insert: %1",addLine))
00151         , KGuiItem(i18n("Save Nevertheless"))
00152         , KStandardGuiItem::cancel(), "OnSave-WrongPythonEncodingHeader");
00153         switch (what) {
00154           case KMessageBox::Yes:
00155           {
00156             int line=firstIsInterpreter?1:0;
00157             QRegExp checkReplace_regex(QString("#\\s*-\\*\\-\\s*coding[:=]\\s*[-\\w]+\\s*-\\*-\\s*$"));
00158             if (checkReplace_regex.exactMatch(document->line(line)))
00159               document->removeLine(line);
00160             document->insertLine(line,addLine);
00161             break;
00162           }
00163           case KMessageBox::No:
00164             return true;
00165             break;
00166           default:
00167             return false;
00168             break;
00169         }
00170       }
00171       return true;
00172     }
00173     virtual void postLoadFilter(KTextEditor::Document*){    }
00174     private:
00175       QRegExp interpreterLine;
00176 };
00177 
00178 class KateDocument::LoadSaveFilterCheckPlugins
00179 {
00180   public:
00181     LoadSaveFilterCheckPlugins() { m_plugins["python-encoding"]=new KatePythonEncodingCheck();}
00182     ~LoadSaveFilterCheckPlugins() {
00183       QHashIterator<QString,LoadSaveFilterCheckPlugin*>i(m_plugins);
00184         while (i.hasNext())
00185           i.next();
00186           delete i.value();
00187       m_plugins.clear();
00188     }
00189     bool preSavePostDialogFilterCheck(const QString& pluginName,KateDocument *document) {
00190       LoadSaveFilterCheckPlugin *plug=getPlugin(pluginName);
00191       if (!plug) return false;
00192       return plug->preSavePostDialogFilterCheck(document);
00193     }
00194     void postLoadFilter(const QString& pluginName,KateDocument *document) {
00195       LoadSaveFilterCheckPlugin *plug=getPlugin(pluginName);
00196       if (!plug) return;
00197       plug->postLoadFilter(document);
00198     }
00199   private:
00200     LoadSaveFilterCheckPlugin *getPlugin(const QString & pluginName)
00201     {
00202       if (!m_plugins.contains(pluginName))
00203       {
00204 #ifdef __GNUC__
00205 #warning implement dynamic loading here
00206 #endif
00207       }
00208       if (!m_plugins.contains(pluginName)) return 0;
00209       return m_plugins[pluginName];
00210     }
00211     QHash <QString,LoadSaveFilterCheckPlugin*> m_plugins;
00212 };
00213 
00214 //BEGIN d'tor, c'tor
00215 //
00216 // KateDocument Constructor
00217 //
00218 KateDocument::KateDocument ( bool bSingleViewMode, bool bBrowserView,
00219                              bool bReadOnly, QWidget *parentWidget,
00220                              QObject *parent)
00221 : KTextEditor::Document (parent),
00222   m_activeView(0L),
00223   m_undoDontMerge(false),
00224   m_undoIgnoreCancel(false),
00225   lastUndoGroupWhenSaved( 0 ),
00226   lastRedoGroupWhenSaved( 0 ),
00227   docWasSavedWhenUndoWasEmpty( true ),
00228   docWasSavedWhenRedoWasEmpty( true ),
00229   m_annotationModel( 0 ),
00230   m_indenter(this),
00231   m_modOnHd (false),
00232   m_modOnHdReason (OnDiskUnmodified),
00233   s_fileChangedDialogsActivated (false),
00234   m_tabInterceptor(0)
00235 {
00236   setComponentData ( KateGlobal::self()->componentData () );
00237 
00238   m_undoComplexMerge=false;
00239 
00240   QString pathName ("/Kate/Document/%1");
00241   pathName = pathName.arg (++dummy);
00242 
00243   // my dbus object
00244   QDBusConnection::sessionBus().registerObject (pathName, this);
00245 
00246   // register doc at factory
00247   KateGlobal::self()->registerDocument(this);
00248 
00249   m_reloading = false;
00250 
00251   m_editHistory = new KateEditHistory(this);
00252   m_smartManager = new KateSmartManager(this);
00253   m_buffer = new KateBuffer(this);
00254 
00255   // init the config object, be careful not to use it
00256   // until the initial readConfig() call is done
00257   m_config = new KateDocumentConfig(this);
00258 
00259   // init some more vars !
00260   setActiveView(0L);
00261 
00262   hlSetByUser = false;
00263   m_fileTypeSetByUser = false;
00264 
00265   editSessionNumber = 0;
00266   editIsRunning = false;
00267   m_editCurrentUndo = 0L;
00268   editWithUndo = false;
00269 
00270   m_docNameNumber = 0;
00271   m_docName = "need init";
00272 
00273   m_bSingleViewMode = bSingleViewMode;
00274   m_bBrowserView = bBrowserView;
00275   m_bReadOnly = bReadOnly;
00276 
00277   setEditableMarks( markType01 );
00278 
00279   m_undoMergeTimer = new QTimer(this);
00280   m_undoMergeTimer->setSingleShot(true);
00281   connect(m_undoMergeTimer, SIGNAL(timeout()), SLOT(undoCancel()));
00282 
00283   clearMarks ();
00284   clearUndo ();
00285   clearRedo ();
00286   setModified (false);
00287   docWasSavedWhenUndoWasEmpty = true;
00288 
00289   // normal hl
00290   m_buffer->setHighlight (0);
00291 
00292   m_blockRemoveTrailingSpaces = false;
00293   m_extension = new KateBrowserExtension( this );
00294 
00295   // important, fill in the config into the indenter we use...
00296   m_indenter.updateConfig ();
00297 
00298   // some nice signals from the buffer
00299   connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int)));
00300   connect(m_buffer, SIGNAL(codeFoldingUpdated()),this,SIGNAL(codeFoldingUpdated()));
00301 
00302   // if the user changes the highlight with the dialog, notify the doc
00303   connect(KateHlManager::self(),SIGNAL(changed()),SLOT(internalHlChanged()));
00304 
00305   // signals for mod on hd
00306   connect( KateGlobal::self()->dirWatch(), SIGNAL(dirty (const QString &)),
00307            this, SLOT(slotModOnHdDirty (const QString &)) );
00308 
00309   connect( KateGlobal::self()->dirWatch(), SIGNAL(created (const QString &)),
00310            this, SLOT(slotModOnHdCreated (const QString &)) );
00311 
00312   connect( KateGlobal::self()->dirWatch(), SIGNAL(deleted (const QString &)),
00313            this, SLOT(slotModOnHdDeleted (const QString &)) );
00314 
00315   // update doc name
00316   setDocName ("");
00317 
00318   // if single view mode, like in the konqui embedding, create a default view ;)
00319   // be lazy, only create it now, if any parentWidget is given, otherwise widget()
00320   // will create it on demand...
00321   if ( m_bSingleViewMode && parentWidget )
00322   {
00323     KTextEditor::View *view = (KTextEditor::View*)createView( parentWidget );
00324     insertChildClient( view );
00325     view->show();
00326     setWidget( view );
00327   }
00328 
00329   connect(this,SIGNAL(sigQueryClose(bool *, bool*)),this,SLOT(slotQueryClose_save(bool *, bool*)));
00330 
00331   m_isasking = 0;
00332 
00333   // register document in plugins
00334   KatePartPluginManager::self()->addDocument(this);
00335 }
00336 
00337 //
00338 // KateDocument Destructor
00339 //
00340 KateDocument::~KateDocument()
00341 {
00342   // Tell the world that we're about to close (== destruct)
00343   // Apps must receive this in a direct signal-slot connection, and prevent
00344   // any further use of interfaces once they return.
00345   emit aboutToClose(this);
00346 
00347   // remove file from dirwatch
00348   deactivateDirWatch ();
00349 
00350   // thanks for offering, KPart, but we're already self-destructing
00351   setAutoDeleteWidget(false);
00352   setAutoDeletePart(false);
00353 
00354   // clean up remaining views
00355   while (!m_views.isEmpty()) {
00356     delete m_views.takeFirst();
00357   }
00358 
00359   delete m_editCurrentUndo;
00360 
00361   // cleanup the undo items, very important, truee :/
00362   qDeleteAll(undoItems);
00363   undoItems.clear();
00364 
00365   // de-register from plugin
00366   KatePartPluginManager::self()->removeDocument(this);
00367 
00368   // cu marks
00369   for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
00370     delete i.value();
00371   m_marks.clear();
00372 
00373   delete m_config;
00374   KateGlobal::self()->deregisterDocument (this);
00375 }
00376 //END
00377 
00378 // on-demand view creation
00379 QWidget *KateDocument::widget()
00380 {
00381   // no singleViewMode -> no widget()...
00382   if (!singleViewMode())
00383     return 0;
00384 
00385   // does a widget exist already? use it!
00386   if (KTextEditor::Document::widget())
00387     return KTextEditor::Document::widget();
00388 
00389   // create and return one...
00390   KTextEditor::View *view = (KTextEditor::View*)createView(0);
00391   insertChildClient( view );
00392   setWidget( view );
00393   return view;
00394 }
00395 
00396 //BEGIN KTextEditor::Document stuff
00397 
00398 KTextEditor::View *KateDocument::createView( QWidget *parent )
00399 {
00400   KateView* newView = new KateView( this, parent);
00401   connect(newView, SIGNAL(cursorPositionChanged(KTextEditor::View*, const KTextEditor::Cursor&)), SLOT(undoCancel()));
00402   if ( s_fileChangedDialogsActivated )
00403     connect( newView, SIGNAL(focusIn( KTextEditor::View * )), this, SLOT(slotModifiedOnDisk()) );
00404 
00405   emit viewCreated (this, newView);
00406 
00407   return newView;
00408 }
00409 
00410 const QList<KTextEditor::View*> &KateDocument::views () const
00411 {
00412   return m_textEditViews;
00413 }
00414 
00415 KTextEditor::Editor *KateDocument::editor ()
00416 {
00417   return KateGlobal::self();
00418 }
00419 
00420 //BEGIN KTextEditor::EditInterface stuff
00421 
00422 QString KateDocument::text() const
00423 {
00424   QString s;
00425 
00426   for (int i = 0; i < m_buffer->count(); i++)
00427   {
00428     KateTextLine::Ptr textLine = m_buffer->plainLine(i);
00429 
00430     if (textLine)
00431     {
00432       s.append (textLine->string());
00433 
00434       if ((i+1) < m_buffer->count())
00435         s.append(QChar::fromAscii('\n'));
00436     }
00437   }
00438 
00439   return s;
00440 }
00441 
00442 QString KateDocument::text( const KTextEditor::Range& range, bool blockwise ) const
00443 {
00444   if (!range.isValid()) {
00445     kWarning() << k_funcinfo << "Text requested for invalid range" << range;
00446     return QString();
00447   }
00448 
00449   if ( blockwise && (range.start().column() > range.end().column()) )
00450     return QString ();
00451 
00452   QString s;
00453 
00454   if (range.start().line() == range.end().line())
00455   {
00456     if (range.start().column() > range.end().column())
00457       return QString ();
00458 
00459     KateTextLine::Ptr textLine = m_buffer->plainLine(range.start().line());
00460 
00461     if ( !textLine )
00462       return QString ();
00463 
00464     return textLine->string(range.start().column(), range.end().column()-range.start().column());
00465   }
00466   else
00467   {
00468 
00469     for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i)
00470     {
00471       KateTextLine::Ptr textLine = m_buffer->plainLine(i);
00472 
00473       if ( !blockwise )
00474       {
00475         if (i == range.start().line())
00476           s.append (textLine->string(range.start().column(), textLine->length()-range.start().column()));
00477         else if (i == range.end().line())
00478           s.append (textLine->string(0, range.end().column()));
00479         else
00480           s.append (textLine->string());
00481       }
00482       else
00483       {
00484         s.append( textLine->string( range.start().column(), range.end().column()-range.start().column()));
00485       }
00486 
00487       if ( i < range.end().line() )
00488         s.append(QChar::fromAscii('\n'));
00489     }
00490   }
00491 
00492   return s;
00493 }
00494 
00495 QChar KateDocument::character( const KTextEditor::Cursor & position ) const
00496 {
00497   KateTextLine::Ptr textLine = m_buffer->plainLine(position.line());
00498 
00499   if ( !textLine )
00500     return QChar();
00501 
00502   if (position.column() >= 0 && position.column() < textLine->string().length())
00503     return textLine->string().at(position.column());
00504 
00505   return QChar();
00506 }
00507 
00508 QStringList KateDocument::textLines( const KTextEditor::Range & range, bool blockwise ) const
00509 {
00510   QStringList ret;
00511 
00512   if (!range.isValid()) {
00513     kWarning() << k_funcinfo << "Text requested for invalid range" << range;
00514     return ret;
00515   }
00516 
00517   if ( blockwise && (range.start().column() > range.end().column()) )
00518     return ret;
00519 
00520   if (range.start().line() == range.end().line())
00521   {
00522     Q_ASSERT(range.start() <= range.end());
00523 
00524     KateTextLine::Ptr textLine = m_buffer->plainLine(range.start().line());
00525 
00526     if ( !textLine )
00527       return ret;
00528 
00529     ret << textLine->string(range.start().column(), range.end().column() - range.start().column());
00530   }
00531   else
00532   {
00533     for (int i = range.start().line(); (i <= range.end().line()) && (i < m_buffer->count()); ++i)
00534     {
00535       KateTextLine::Ptr textLine = m_buffer->plainLine(i);
00536 
00537       if ( !blockwise )
00538       {
00539         if (i == range.start().line())
00540           ret << textLine->string(range.start().column(), textLine->length() - range.start().column());
00541         else if (i == range.end().line())
00542           ret << textLine->string(0, range.end().column());
00543         else
00544           ret << textLine->string();
00545       }
00546       else
00547       {
00548         ret << textLine->string(range.start().column(), range.end().column() - range.start().column());
00549       }
00550     }
00551   }
00552 
00553   return ret;
00554 }
00555 
00556 QString KateDocument::line( int line ) const
00557 {
00558   KateTextLine::Ptr l = m_buffer->plainLine(line);
00559 
00560   if (!l)
00561     return QString();
00562 
00563   return l->string();
00564 }
00565 
00566 bool KateDocument::setText(const QString &s)
00567 {
00568   if (!isReadWrite())
00569     return false;
00570 
00571   QList<KTextEditor::Mark> msave;
00572 
00573   foreach (KTextEditor::Mark* mark, m_marks)
00574     msave.append(*mark);
00575 
00576   editStart ();
00577 
00578   // delete the text
00579   clear();
00580 
00581   // insert the new text
00582   insertText (KTextEditor::Cursor(), s);
00583 
00584   editEnd ();
00585 
00586   foreach (const KTextEditor::Mark& mark, msave)
00587     setMark (mark.line, mark.type);
00588 
00589   return true;
00590 }
00591 
00592 bool KateDocument::setText( const QStringList & text )
00593 {
00594   if (!isReadWrite())
00595     return false;
00596 
00597   QList<KTextEditor::Mark> msave;
00598 
00599   foreach (KTextEditor::Mark* mark, m_marks)
00600     msave.append(*mark);
00601 
00602   editStart ();
00603 
00604   // delete the text
00605   clear();
00606 
00607   // insert the new text
00608   insertText (KTextEditor::Cursor::start(), text);
00609 
00610   editEnd ();
00611 
00612   foreach (const KTextEditor::Mark& mark, msave)
00613     setMark (mark.line, mark.type);
00614 
00615   return true;
00616 }
00617 
00618 bool KateDocument::clear()
00619 {
00620   if (!isReadWrite())
00621     return false;
00622 
00623   foreach (KateView *view, m_views) {
00624     view->clear();
00625     view->tagAll();
00626     view->update();
00627   }
00628 
00629   clearMarks ();
00630 
00631   return removeText (KTextEditor::Range(KTextEditor::Cursor(), KTextEditor::Cursor(lastLine()+1, 0)));
00632 }
00633 
00634 bool KateDocument::insertText( const KTextEditor::Cursor& position, const QString& text, bool block )
00635 {
00636   if (!isReadWrite())
00637     return false;
00638 
00639   if (text.isEmpty())
00640     return true;
00641 
00642   editStart();
00643 
00644   int currentLine = position.line();
00645   int currentLineStart = 0;
00646   int totalLength = text.length();
00647   int insertColumn = position.column();
00648 
00649   if (position.line() > lines())
00650   {
00651     int line = lines();
00652     while (line != position.line() + totalLength + 1)
00653     {
00654       editInsertLine(line,"");
00655       line++;
00656     }
00657   }
00658 
00659   bool replacetabs = ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn );
00660   int tabWidth = config()->tabWidth();
00661 
00662   static const QChar newLineChar('\n');
00663   static const QChar tabChar('\t');
00664   static const QChar spaceChar(' ');
00665 
00666   int insertColumnExpanded = insertColumn;
00667   KateTextLine::Ptr l = m_buffer->line( currentLine );
00668   if (l)
00669     insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00670 
00671   int pos = 0;
00672   for (; pos < totalLength; pos++)
00673   {
00674     const QChar& ch = text.at(pos);
00675 
00676     if (ch == newLineChar)
00677     {
00678       // Only perform the text insert if there is text to insert
00679       if (currentLineStart < pos)
00680         editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
00681 
00682       if ( !block )
00683       {
00684         editWrapLine(currentLine, insertColumn + pos - currentLineStart);
00685         insertColumn = 0;
00686       }
00687       else
00688       {
00689         if ( currentLine == lastLine() )
00690           editWrapLine(currentLine , insertColumn + pos - currentLineStart);
00691         insertColumn = position.column(); // tab expansion might change this
00692       }
00693 
00694       currentLine++;
00695       currentLineStart = pos + 1;
00696       l = m_buffer->line( currentLine );
00697       if (l)
00698         insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00699     }
00700     else
00701     {
00702       if ( replacetabs && ch == tabChar )
00703       {
00704         int spacesRequired = tabWidth - ( (insertColumnExpanded + pos - currentLineStart) % tabWidth );
00705         editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart) + QString(spacesRequired, spaceChar));
00706 
00707         insertColumn += pos - currentLineStart + spacesRequired;
00708         currentLineStart = pos + 1;
00709         l = m_buffer->line( currentLine );
00710         if (l)
00711           insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00712       }
00713     }
00714   }
00715 
00716   // Only perform the text insert if there is text to insert
00717   if (currentLineStart < pos)
00718     editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
00719 
00720   editEnd();
00721   return true;
00722 }
00723 
00724 bool KateDocument::insertText( const KTextEditor::Cursor & position, const QStringList & textLines, bool block )
00725 {
00726   if (!isReadWrite())
00727     return false;
00728 
00729   if (textLines.isEmpty() || (textLines.count() == 1 && textLines.first().isEmpty()))
00730     return true;
00731 
00732   // FIXME - huh, contradicted below
00733   if (position.line() > lines())
00734     return false;
00735 
00736   editStart();
00737 
00738   if (position.line() > lines())
00739     editInsertLine(position.line(),"");
00740 
00741   int currentLine = position.line();
00742   int currentLineStart = 0;
00743   int insertColumn = position.column();
00744 
00745   bool replacetabs = ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn );
00746   int tabWidth = config()->tabWidth();
00747 
00748   static const QChar newLineChar('\n');
00749   static const QChar tabChar('\t');
00750   static const QChar spaceChar(' ');
00751 
00752   int insertColumnExpanded = insertColumn;
00753   KateTextLine::Ptr l = m_buffer->line( currentLine );
00754   if (l)
00755     insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00756 
00757   foreach (const QString &text, textLines)
00758   {
00759     int pos = 0;
00760     for (; pos < text.length(); pos++)
00761     {
00762       const QChar& ch = text.at(pos);
00763 
00764       if (ch == newLineChar)
00765       {
00766         // Only perform the text insert if there is text to insert
00767         if (currentLineStart < pos)
00768           editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
00769 
00770         if ( !block )
00771         {
00772           editWrapLine(currentLine, pos + insertColumn);
00773           insertColumn = 0;
00774         }
00775         else
00776         {
00777           if ( currentLine == lastLine() )
00778             editWrapLine(currentLine , insertColumn + pos - currentLineStart);
00779           insertColumn = position.column(); // tab expansion might change this
00780         }
00781 
00782         currentLine++;
00783         currentLineStart = pos + 1;
00784         l = m_buffer->line( currentLine );
00785         if (l)
00786           insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00787       }
00788       else
00789       {
00790         if ( replacetabs && ch == tabChar )
00791         {
00792           int spacesRequired = tabWidth - ( (insertColumnExpanded + pos - currentLineStart) % tabWidth );
00793           editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart) + QString(spacesRequired, spaceChar));
00794 
00795           insertColumn += pos - currentLineStart + spacesRequired;
00796           l = m_buffer->line( currentLine );
00797           if (l)
00798             insertColumnExpanded = l->toVirtualColumn( insertColumn, tabWidth );
00799           currentLineStart = pos + 1;
00800         }
00801       }
00802     }
00803 
00804     // Only perform the text insert if there is text to insert
00805     if (currentLineStart < pos - currentLineStart)
00806       editInsertText(currentLine, insertColumn, text.mid(currentLineStart, pos - currentLineStart));
00807   }
00808 
00809   editEnd();
00810   return true;
00811 }
00812 
00813 
00814 bool KateDocument::removeText ( const KTextEditor::Range &_range, bool block )
00815 {
00816   KTextEditor::Range range = _range;
00817 
00818   if (!isReadWrite())
00819     return false;
00820 
00821   if ( block && (range.start().column() > range.end().column()) )
00822     return false;
00823 
00824   // Should now be impossible to trigger with the new Range class
00825   Q_ASSERT( range.start().line() <= range.end().line() );
00826 
00827   if ( range.start().line() > lastLine() )
00828     return false;
00829 
00830   if (!block)
00831     emit aboutToRemoveText(range);
00832 
00833   editStart();
00834 
00835   if ( !block )
00836   {
00837     if ( range.end().line() > lastLine() )
00838     {
00839       range.end().setPosition(lastLine()+1, 0);
00840     }
00841 
00842     if (range.onSingleLine())
00843     {
00844       editRemoveText(range.start().line(), range.start().column(), range.columnWidth());
00845     }
00846     else if (range.start().line() + 1 == range.end().line())
00847     {
00848       if ( (m_buffer->plainLine(range.start().line())->length() - range.start().column()) > 0 )
00849         editRemoveText(range.start().line(), range.start().column(), m_buffer->plainLine(range.start().line())->length() - range.start().column());
00850 
00851       if (range.end().column())
00852         editRemoveText (range.start().line() + 1, 0, range.end().column());
00853 
00854       editUnWrapLine (range.start().line());
00855     }
00856     else
00857     {
00858       for (int line = range.end().line(); line >= range.start().line(); --line)
00859       {
00860         if ((line > range.start().line()) && (line < range.end().line())) {
00861           editRemoveLine(line);
00862 
00863         } else if (line == range.end().line()) {
00864           if ( range.end().line() <= lastLine() )
00865             editRemoveText(line, 0, range.end().column());
00866 
00867         } else {
00868           if ( (m_buffer->plainLine(line)->length() - range.start().column()) > 0 )
00869             editRemoveText(line, range.start().column(), m_buffer->plainLine(line)->length() - range.start().column());
00870 
00871           editUnWrapLine(range.start().line());
00872         }
00873 
00874         if ( line == 0 )
00875           break;
00876       }
00877     }
00878   } // if ( ! block )
00879   else
00880   {
00881     int startLine = qMax(0, range.start().line());
00882     for (int line = qMin(range.end().line(), lastLine()); line >= startLine; --line)
00883       editRemoveText(line, range.start().column(), range.end().column() - range.start().column());
00884   }
00885 
00886   editEnd ();
00887   emit textRemoved();
00888   return true;
00889 }
00890 
00891 bool KateDocument::insertLine( int l, const QString &str )
00892 {
00893   if (!isReadWrite())
00894     return false;
00895 
00896   if (l < 0 || l > lines())
00897     return false;
00898 
00899   return editInsertLine (l, str);
00900 }
00901 
00902 bool KateDocument::insertLines( int line, const QStringList & text )
00903 {
00904   if (!isReadWrite())
00905     return false;
00906 
00907   if (line < 0 || line > lines())
00908     return false;
00909 
00910   bool success = true;
00911   foreach (const QString &string, text)
00912     // FIXME assumes no \n in each string
00913     success &= editInsertLine (line++, string);
00914 
00915   return success;
00916 }
00917 
00918 bool KateDocument::removeLine( int line )
00919 {
00920   if (!isReadWrite())
00921     return false;
00922 
00923   if (line < 0 || line > lastLine())
00924     return false;
00925 
00926   return editRemoveLine (line);
00927 }
00928 
00929 int KateDocument::totalCharacters() const
00930 {
00931   int l = 0;
00932 
00933   for (int i = 0; i < m_buffer->count(); ++i)
00934   {
00935     KateTextLine::Ptr line = m_buffer->plainLine(i);
00936 
00937     if (line)
00938       l += line->length();
00939   }
00940 
00941   return l;
00942 }
00943 
00944 int KateDocument::lines() const
00945 {
00946   return m_buffer->count();
00947 }
00948 
00949 int KateDocument::numVisLines() const
00950 {
00951   return m_buffer->countVisible ();
00952 }
00953 
00954 int KateDocument::lineLength ( int line ) const
00955 {
00956   if (line < 0 || line > lastLine())
00957     return -1;
00958 
00959   KateTextLine::Ptr l = m_buffer->plainLine(line);
00960 
00961   if (!l)
00962     return -1;
00963 
00964   return l->length();
00965 }
00966 //END
00967 
00968 //BEGIN KTextEditor::EditInterface internal stuff
00969 //
00970 // Starts an edit session with (or without) undo, update of view disabled during session
00971 //
00972 void KateDocument::editStart (bool withUndo, Kate::EditSource editSource)
00973 {
00974   editSessionNumber++;
00975 
00976   if (editSource == Kate::NoEditSource)
00977     m_editSources.push(m_editSources.isEmpty() ? Kate::UserInputEdit : m_editSources.top());
00978   else
00979     m_editSources.push(editSource);
00980 
00981   if (editSessionNumber > 1)
00982     return;
00983 
00984   // Unlocked in editEnd
00985   smartMutex()->lock();
00986 
00987   editIsRunning = true;
00988   editWithUndo = withUndo;
00989 
00990   if (editWithUndo)
00991     undoStart();
00992   else
00993     undoCancel();
00994 
00995   foreach(KateView *view,m_views)
00996   {
00997     view->editStart ();
00998   }
00999 
01000   m_buffer->editStart ();
01001 }
01002 
01003 void KateDocument::undoStart()
01004 {
01005   if (m_editCurrentUndo || (m_activeView && activeKateView()->imComposeEvent())) return;
01006 
01007   // new current undo item
01008   m_editCurrentUndo = new KateUndoGroup(this);
01009 }
01010 
01011 void KateDocument::undoEnd()
01012 {
01013   if (m_activeView && activeKateView()->imComposeEvent())
01014     return;
01015 
01016   if (m_editCurrentUndo)
01017   {
01018     bool changedUndo = false;
01019 
01020     if (m_editCurrentUndo->isEmpty())
01021       delete m_editCurrentUndo;
01022     else if (!m_undoDontMerge && !undoItems.isEmpty() && undoItems.last() && undoItems.last()->merge(m_editCurrentUndo,m_undoComplexMerge))
01023       delete m_editCurrentUndo;
01024     else
01025     {
01026       undoItems.append(m_editCurrentUndo);
01027       changedUndo = true;
01028     }
01029 
01030     m_undoDontMerge = false;
01031     m_undoIgnoreCancel = true;
01032 
01033     m_editCurrentUndo = 0L;
01034 
01035     // (Re)Start the single-shot timer to cancel the undo merge
01036     // the user has 5 seconds to input more data, or undo merging gets canceled for the current undo item.
01037     m_undoMergeTimer->start(5000);
01038 
01039     if (changedUndo)
01040       emit undoChanged();
01041   }
01042 }
01043 
01044 void KateDocument::undoCancel()
01045 {
01046   // Don't worry about this when an edit is in progress
01047   if (editIsRunning)
01048     return;
01049 
01050   if (m_undoIgnoreCancel) {
01051     m_undoIgnoreCancel = false;
01052     return;
01053   }
01054 
01055   m_undoDontMerge = true;
01056 
01057   Q_ASSERT(!m_editCurrentUndo);
01058 
01059   // As you can see by the above assert, neither of these should really be required
01060   delete m_editCurrentUndo;
01061   m_editCurrentUndo = 0L;
01062 }
01063 
01064 void KateDocument::undoSafePoint() {
01065   Q_ASSERT(m_editCurrentUndo);
01066   if (!m_editCurrentUndo) return;
01067   m_editCurrentUndo->safePoint();
01068 }
01069 
01070 //
01071 // End edit session and update Views
01072 //
01073 void KateDocument::editEnd ()
01074 {
01075   if (editSessionNumber == 0)
01076     return;
01077 
01078   // wrap the new/changed text, if something really changed!
01079   if (m_buffer->editChanged() && (editSessionNumber == 1))
01080     if (editWithUndo && config()->wordWrap())
01081       wrapText (m_buffer->editTagStart(), m_buffer->editTagEnd());
01082 
01083   editSessionNumber--;
01084 
01085   m_editSources.pop();
01086 
01087   if (editSessionNumber > 0)
01088     return;
01089 
01090   // end buffer edit, will trigger hl update
01091   // this will cause some possible adjustment of tagline start/end
01092   m_buffer->editEnd ();
01093 
01094   if (editWithUndo)
01095     undoEnd();
01096 
01097   // Was locked in editStart
01098   smartMutex()->unlock();
01099 
01100   // edit end for all views !!!!!!!!!
01101   foreach(KateView *view, m_views)
01102     view->editEnd (m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom());
01103 
01104   if (m_buffer->editChanged())
01105   {
01106     setModified(true);
01107     emit textChanged (this);
01108   }
01109 
01110   editIsRunning = false;
01111 }
01112 
01113 void KateDocument::pushEditState ()
01114 {
01115   editStateStack.push(editSessionNumber);
01116 }
01117 
01118 void KateDocument::popEditState ()
01119 {
01120   if (editStateStack.isEmpty()) return;
01121 
01122   int count = editStateStack.pop() - editSessionNumber;
01123   while (count < 0) { ++count; editEnd(); }
01124   while (count > 0) { --count; editStart(); }
01125 }
01126 
01127 bool KateDocument::wrapText(int startLine, int endLine)
01128 {
01129   if (startLine < 0 || endLine < 0)
01130     return false;
01131 
01132   if (!isReadWrite())
01133     return false;
01134 
01135   int col = config()->wordWrapAt();
01136 
01137   if (col == 0)
01138     return false;
01139 
01140   editStart ();
01141 
01142   for (int line = startLine; (line <= endLine) && (line < lines()); line++)
01143   {
01144     KateTextLine::Ptr l = m_buffer->line(line);
01145 
01146     if (!l)
01147       return false;
01148 
01149     kDebug (13020) << "try wrap line: " << line;
01150 
01151     if (l->virtualLength(m_buffer->tabWidth()) > col)
01152     {
01153       KateTextLine::Ptr nextl = m_buffer->line(line+1);
01154 
01155       kDebug (13020) << "do wrap line: " << line;
01156 
01157       int eolPosition = l->length()-1;
01158 
01159       // take tabs into account here, too
01160       int x = 0;
01161       const QString & t = l->string();
01162       int z2 = 0;
01163       for ( ; z2 < l->length(); z2++)
01164       {
01165         static const QChar tabChar('\t');
01166         if (t.at(z2) == tabChar)
01167           x += m_buffer->tabWidth() - (x % m_buffer->tabWidth());
01168         else
01169           x++;
01170 
01171         if (x > col)
01172           break;
01173       }
01174 
01175       int searchStart = qMin (z2, l->length()-1);
01176 
01177       // If where we are wrapping is an end of line and is a space we don't
01178       // want to wrap there
01179       if (searchStart == eolPosition && t.at(searchStart).isSpace())
01180         searchStart--;
01181 
01182       // Scan backwards looking for a place to break the line
01183       // We are not interested in breaking at the first char
01184       // of the line (if it is a space), but we are at the second
01185       // anders: if we can't find a space, try breaking on a word
01186       // boundary, using KateHighlight::canBreakAt().
01187       // This could be a priority (setting) in the hl/filetype/document
01188       int z = 0;
01189       int nw = 0; // alternative position, a non word character
01190       for (z=searchStart; z > 0; z--)
01191       {
01192         if (t.at(z).isSpace()) break;
01193         if ( ! nw && highlight()->canBreakAt( t.at(z) , l->attribute(z) ) )
01194         nw = z;
01195       }
01196 
01197       bool removeTrailingSpace = false;
01198       if (z > 0)
01199       {
01200         // So why don't we just remove the trailing space right away?
01201         // Well, the (view's) cursor may be directly in front of that space
01202         // (user typing text before the last word on the line), and if that
01203         // happens, the cursor would be moved to the next line, which is not
01204         // what we want (bug #106261)
01205         z++;
01206         removeTrailingSpace = true;
01207       }
01208       else
01209       {
01210         // There was no space to break at so break at a nonword character if
01211         // found, or at the wrapcolumn ( that needs be configurable )
01212         // Don't try and add any white space for the break
01213         if ( nw && nw < col ) nw++; // break on the right side of the character
01214         z = nw ? nw : col;
01215       }
01216 
01217       if (nextl && !nextl->isAutoWrapped())
01218       {
01219         editWrapLine (line, z, true);
01220         editMarkLineAutoWrapped (line+1, true);
01221 
01222         endLine++;
01223       }
01224       else
01225       {
01226         if (nextl && (nextl->length() > 0) && !nextl->at(0).isSpace() && ((l->length() < 1) || !l->at(l->length()-1).isSpace()))
01227           editInsertText (line+1, 0, QString (" "));
01228 
01229         bool newLineAdded = false;
01230         editWrapLine (line, z, false, &newLineAdded);
01231 
01232         editMarkLineAutoWrapped (line+1, true);
01233 
01234         endLine++;
01235       }
01236 
01237       if (removeTrailingSpace) {
01238         // cu space
01239         editRemoveText (line, z - 1, 1);
01240       }
01241     }
01242   }
01243 
01244   editEnd ();
01245 
01246   return true;
01247 }
01248 
01249 void KateDocument::editAddUndo (int type, uint line, uint col, uint len, const QString &text)
01250 {
01251   if (editIsRunning && editWithUndo && m_editCurrentUndo) {
01252     // save selection and cursor position
01253     if (KateView *view = activeKateView()) {
01254       m_editCurrentUndo->setCursorPosition(view->cursorPosition());
01255       m_editCurrentUndo->setSelection(view->selectionRange());
01256     }
01257     m_editCurrentUndo->addItem(static_cast<KateUndoGroup::UndoType>(type), line, col, len, text);
01258 
01259     // Clear redo buffer
01260     if (redoItems.count()) {
01261       qDeleteAll(redoItems);
01262       redoItems.clear();
01263     }
01264   }
01265 }
01266 
01267 bool KateDocument::editInsertText ( int line, int col, const QString &s, Kate::EditSource editSource )
01268 {
01269   if (line < 0 || col < 0)
01270     return false;
01271 
01272   if (!isReadWrite())
01273     return false;
01274 
01275   KateTextLine::Ptr l = m_buffer->line(line);
01276 
01277   if (!l)
01278     return false;
01279 
01280   editStart (true, editSource);
01281 
01282   editAddUndo (KateUndoGroup::editInsertText, line, col, s.length(), s);
01283 
01284   l->insertText (col, s);
01285 
01286   m_buffer->changeLine(line);
01287 
01288   history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(line, col, line, col), QStringList(), KTextEditor::Range(line, col, line, col + s.length()), QStringList(s)) );
01289   emit KTextEditor::Document::textInserted(this, KTextEditor::Range(line, col, line, col + s.length()));
01290 
01291   editEnd();
01292 
01293   return true;
01294 }
01295 
01296 bool KateDocument::editRemoveText ( int line, int col, int len, Kate::EditSource editSource )
01297 {
01298   if (line < 0 || col < 0 || len < 0)
01299     return false;
01300 
01301   if (!isReadWrite())
01302     return false;
01303 
01304   KateTextLine::Ptr l = m_buffer->line(line);
01305 
01306   if (!l)
01307     return false;
01308 
01309   editStart (true, editSource);
01310 
01311   editAddUndo (KateUndoGroup::editRemoveText, line, col, len, l->string().mid(col, len));
01312 
01313   l->removeText (col, len);
01314   removeTrailingSpace( line );
01315 
01316   m_buffer->changeLine(line);
01317 
01318   history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(line, col, line, col + len), QStringList(l->string().mid(col, len)), KTextEditor::Range(line, col, line, col), QStringList()) );
01319   emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line, col + len));
01320 
01321   editEnd ();
01322 
01323   return true;
01324 }
01325 
01326 bool KateDocument::editMarkLineAutoWrapped ( int line, bool autowrapped )
01327 {
01328   if (line < 0)
01329     return false;
01330 
01331   if (!isReadWrite())
01332     return false;
01333 
01334   KateTextLine::Ptr l = m_buffer->line(line);
01335 
01336   if (!l)
01337     return false;
01338 
01339   editStart ();
01340 
01341   editAddUndo (KateUndoGroup::editMarkLineAutoWrapped, line, autowrapped ? 1 : 0, 0, QString());
01342 
01343   l->setAutoWrapped (autowrapped);
01344 
01345   m_buffer->changeLine(line);
01346 
01347   editEnd ();
01348 
01349   return true;
01350 }
01351 
01352 bool KateDocument::editWrapLine ( int line, int col, bool newLine, bool *newLineAdded)
01353 {
01354   if (line < 0 || col < 0)
01355     return false;
01356 
01357   if (!isReadWrite())
01358     return false;
01359 
01360   KateTextLine::Ptr l = m_buffer->line(line);
01361 
01362   if (!l)
01363     return false;
01364 
01365   editStart ();
01366 
01367   KateTextLine::Ptr nextLine = m_buffer->line(line+1);
01368 
01369   int pos = l->length() - col;
01370 
01371   if (pos < 0)
01372     pos = 0;
01373 
01374   editAddUndo (KateUndoGroup::editWrapLine, line, col, pos, (!nextLine || newLine) ? "1" : "0");
01375 
01376   if (!nextLine || newLine)
01377   {
01378     KateTextLine::Ptr textLine(new KateTextLine());
01379 
01380     textLine->insertText (0, l->string().mid(col, pos));
01381     l->truncate(col);
01382 
01383     m_buffer->insertLine (line+1, textLine);
01384     m_buffer->changeLine(line);
01385 
01386     QList<KTextEditor::Mark*> list;
01387     for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
01388     {
01389       if( i.value()->line >= line )
01390       {
01391         if ((col == 0) || (i.value()->line > line))
01392           list.append( i.value() );
01393       }
01394     }
01395 
01396     for( int i=0; i < list.size(); ++i )
01397     {
01398       KTextEditor::Mark* mark = m_marks.take( list[i]->line );
01399       mark->line++;
01400       m_marks.insert( mark->line, mark );
01401     }
01402 
01403     if( !list.isEmpty() )
01404       emit marksChanged( this );
01405 
01406     // yes, we added a new line !
01407     if (newLineAdded)
01408       (*newLineAdded) = true;
01409 
01410     history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(line, col, line, col + pos), QStringList(), KTextEditor::Range(line, col, line+1, 0), QStringList()) );
01411   }
01412   else
01413   {
01414     nextLine->insertText (0, l->string().mid(col, pos));
01415     l->truncate(col);
01416 
01417     m_buffer->changeLine(line);
01418     m_buffer->changeLine(line+1);
01419 
01420     // no, no new line added !
01421     if (newLineAdded)
01422       (*newLineAdded) = false;
01423 
01424     history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(line, col, line+1, 0), QStringList(), KTextEditor::Range(line, col, line+1, pos), QStringList()) );
01425   }
01426 
01427   emit KTextEditor::Document::textInserted(this, KTextEditor::Range(line, col, line+1, pos));
01428 
01429   editEnd ();
01430 
01431   return true;
01432 }
01433 
01434 bool KateDocument::editUnWrapLine ( int line, bool removeLine, int length )
01435 {
01436   if (line < 0 || length < 0)
01437     return false;
01438 
01439   if (!isReadWrite())
01440     return false;
01441 
01442   KateTextLine::Ptr l = m_buffer->line(line);
01443   KateTextLine::Ptr nextLine = m_buffer->line(line+1);
01444 
01445   if (!l || !nextLine)
01446     return false;
01447 
01448   editStart ();
01449 
01450   int col = l->length ();
01451 
01452   editAddUndo (KateUndoGroup::editUnWrapLine, line, col, length, removeLine ? "1" : "0");
01453 
01454   if (removeLine)
01455   {
01456     l->insertText (col, nextLine->string());
01457 
01458     m_buffer->changeLine(line);
01459     m_buffer->removeLine(line+1);
01460   }
01461   else
01462   {
01463     l->insertText (col, nextLine->string().left((nextLine->length() < length) ? nextLine->length() : length));
01464     nextLine->removeText (0, (nextLine->length() < length) ? nextLine->length() : length);
01465 
01466     m_buffer->changeLine(line);
01467     m_buffer->changeLine(line+1);
01468   }
01469 
01470   QList<KTextEditor::Mark*> list;
01471   for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
01472   {
01473     if( i.value()->line >= line+1 )
01474       list.append( i.value() );
01475 
01476     if ( i.value()->line == line+1 )
01477     {
01478       KTextEditor::Mark* mark = m_marks.take( line );
01479 
01480       if (mark)
01481       {
01482         i.value()->type |= mark->type;
01483       }
01484     }
01485   }
01486 
01487   for( int i=0; i < list.size(); ++i )
01488     {
01489     KTextEditor::Mark* mark = m_marks.take( list[i]->line );
01490     mark->line--;
01491     m_marks.insert( mark->line, mark );
01492   }
01493 
01494   if( !list.isEmpty() )
01495     emit marksChanged( this );
01496 
01497   history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(line, col, line+1, 0), QStringList(QString()), KTextEditor::Range(line, col, line, col), QStringList()) );
01498   emit KTextEditor::Document::textRemoved(this, KTextEditor::Range(line, col, line+1, 0));
01499 
01500   editEnd ();
01501 
01502   return true;
01503 }
01504 
01505 bool KateDocument::editInsertLine ( int line, const QString &s, Kate::EditSource editSource )
01506 {
01507   if (line < 0)
01508     return false;
01509 
01510   if (!isReadWrite())
01511     return false;
01512 
01513   if ( line > lines() )
01514     return false;
01515 
01516   editStart (true, editSource);
01517 
01518   editAddUndo (KateUndoGroup::editInsertLine, line, 0, s.length(), s);
01519 
01520   removeTrailingSpace( line ); // old line
01521 
01522   KateTextLine::Ptr tl(new KateTextLine());
01523   tl->insertText (0, s);
01524   m_buffer->insertLine(line, tl);
01525   m_buffer->changeLine(line);
01526 
01527   removeTrailingSpace( line ); // new line
01528 
01529   QList<KTextEditor::Mark*> list;
01530   for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
01531   {
01532     if( i.value()->line >= line )
01533       list.append( i.value() );
01534   }
01535 
01536   for( int i=0; i < list.size(); ++i )
01537   {
01538     KTextEditor::Mark* mark = m_marks.take( list[i]->line );
01539     mark->line++;
01540     m_marks.insert( mark->line, mark );
01541   }
01542 
01543   if( !list.isEmpty() )
01544     emit marksChanged( this );
01545 
01546   KTextEditor::Range rangeInserted(line, 0, line, tl->length());
01547 
01548   if (line) {
01549     KateTextLine::Ptr prevLine = plainKateTextLine(line - 1);
01550     rangeInserted.start().setPosition(line - 1, prevLine->length());
01551   } else {
01552     rangeInserted.end().setPosition(line + 1, 0);
01553   }
01554 
01555   history()->doEdit( new KateEditInfo(this, m_editSources.top(), KTextEditor::Range(rangeInserted.start(), rangeInserted.start()), QStringList(), rangeInserted, QStringList(s)) );
01556   emit KTextEditor::Document::textInserted(this, rangeInserted);
01557 
01558   editEnd ();
01559 
01560   return true;
01561 }
01562 
01563 bool KateDocument::editRemoveLine ( int line, Kate::EditSource editSource )
01564 {
01565   if (line < 0)
01566     return false;
01567 
01568   if (!isReadWrite())
01569     return false;
01570 
01571   if ( line > lastLine() )
01572     return false;
01573 
01574   if ( lines() == 1 )
01575     return editRemoveText (0, 0, m_buffer->line(0)->length());
01576 
01577   editStart (true, editSource);
01578 
01579   QString oldText = this->line(line);
01580 
01581   editAddUndo (KateUndoGroup::editRemoveLine, line, 0, lineLength(line), this->line(line));
01582 
01583   KTextEditor::Range rangeRemoved(line, 0, line, oldText.length());
01584 
01585   if (line < lastLine()) {
01586     rangeRemoved.end().setPosition(line + 1, 0);
01587   } else if (line) {
01588     KateTextLine::Ptr prevLine = plainKateTextLine(line - 1);
01589     rangeRemoved.start().setPosition(line - 1, prevLine->length());
01590   }
01591 
01592   m_buffer->removeLine(line);
01593 
01594   KTextEditor::Mark* rmark = 0;
01595   QList<KTextEditor::Mark*> list;
01596   for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
01597   {
01598     if ( (i.value()->line > line) )
01599       list.append( i.value() );
01600     else if ( (i.value()->line == line) )
01601       rmark = i.value();
01602   }
01603 
01604   if (rmark)
01605     delete (m_marks.take (rmark->line));
01606 
01607   for( int i=0; i < list.size(); ++i )
01608   {
01609     KTextEditor::Mark* mark = m_marks.take( list[i]->line );
01610     mark->line--;
01611     m_marks.insert( mark->line, mark );
01612   }
01613 
01614   if( !list.isEmpty() )
01615     emit marksChanged( this );
01616 
01617   history()->doEdit( new KateEditInfo(this, m_editSources.top(), rangeRemoved, QStringList(QString(oldText)), KTextEditor::Range(rangeRemoved.start(), rangeRemoved.start()), QStringList()) );
01618   emit KTextEditor::Document::textRemoved(this, rangeRemoved);
01619 
01620   editEnd();
01621 
01622   return true;
01623 }
01624 //END
01625 
01626 //BEGIN KTextEditor::UndoInterface stuff
01627 
01628 uint KateDocument::undoCount () const
01629 {
01630   return undoItems.count ();
01631 }
01632 
01633 uint KateDocument::redoCount () const
01634 {
01635   return redoItems.count ();
01636 }
01637 
01638 void KateDocument::undo()
01639 {
01640   if ((undoItems.count() > 0) && undoItems.last())
01641   {
01642     //clearSelection ();
01643 
01644     undoItems.last()->undo();
01645     redoItems.append (undoItems.last());
01646     undoItems.removeLast ();
01647     updateModified();
01648 
01649     emit undoChanged ();
01650   }
01651 }
01652 
01653 void KateDocument::redo()
01654 {
01655   if ((redoItems.count() > 0) && redoItems.last())
01656   {
01657     //clearSelection ();
01658 
01659     redoItems.last()->redo();
01660     undoItems.append (redoItems.last());
01661     redoItems.removeLast ();
01662     updateModified();
01663 
01664     emit undoChanged ();
01665   }
01666 }
01667 
01668 void KateDocument::updateModified()
01669 {
01670   /*
01671   How this works:
01672 
01673     After noticing that there where to many scenarios to take into
01674     consideration when using 'if's to toggle the "Modified" flag
01675     I came up with this baby, flexible and repetitive calls are
01676     minimal.
01677 
01678     A numeric unique pattern is generated by toggleing a set of bits,
01679     each bit symbolizes a different state in the Undo Redo structure.
01680 
01681       undoItems.isEmpty() != null          BIT 1
01682       redoItems.isEmpty() != null          BIT 2
01683       docWasSavedWhenUndoWasEmpty == true  BIT 3
01684       docWasSavedWhenRedoWasEmpty == true  BIT 4
01685       lastUndoGroupWhenSavedIsLastUndo     BIT 5
01686       lastUndoGroupWhenSavedIsLastRedo     BIT 6
01687       lastRedoGroupWhenSavedIsLastUndo     BIT 7
01688       lastRedoGroupWhenSavedIsLastRedo     BIT 8
01689 
01690     If you find a new pattern, please add it to the patterns array
01691   */
01692 
01693   unsigned char currentPattern = 0;
01694   const unsigned char patterns[] = {5,16,24,26,88,90,93,133,144,149,165};
01695   const unsigned char patternCount = sizeof(patterns);
01696   KateUndoGroup* undoLast = 0;
01697   KateUndoGroup* redoLast = 0;
01698 
01699   if (undoItems.isEmpty())
01700   {
01701     currentPattern |= 1;
01702   }
01703   else
01704   {
01705     undoLast = undoItems.last();
01706   }
01707 
01708   if (redoItems.isEmpty())
01709   {
01710     currentPattern |= 2;
01711   }
01712   else
01713   {
01714     redoLast = redoItems.last();
01715   }
01716 
01717   if (docWasSavedWhenUndoWasEmpty) currentPattern |= 4;
01718   if (docWasSavedWhenRedoWasEmpty) currentPattern |= 8;
01719   if (lastUndoGroupWhenSaved == undoLast) currentPattern |= 16;
01720   if (lastUndoGroupWhenSaved == redoLast) currentPattern |= 32;
01721   if (lastRedoGroupWhenSaved == undoLast) currentPattern |= 64;
01722   if (lastRedoGroupWhenSaved == redoLast) currentPattern |= 128;
01723 
01724   // This will print out the pattern information
01725 
01726   kDebug(13020) << "Pattern:" << static_cast<unsigned int>(currentPattern);
01727 
01728   for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex)
01729   {
01730     if ( currentPattern == patterns[patternIndex] )
01731     {
01732       setModified( false );
01733       kDebug(13020) << "setting modified to false!";
01734       break;
01735     }
01736   }
01737 }
01738 
01739 void KateDocument::clearUndo()
01740 {
01741   qDeleteAll(undoItems);
01742   undoItems.clear ();
01743 
01744   lastUndoGroupWhenSaved = 0;
01745   docWasSavedWhenUndoWasEmpty = false;
01746 
01747   emit undoChanged ();
01748 }
01749 
01750 void KateDocument::clearRedo()
01751 {
01752   qDeleteAll(redoItems);
01753   redoItems.clear ();
01754 
01755   lastRedoGroupWhenSaved = 0;
01756   docWasSavedWhenRedoWasEmpty = false;
01757 
01758   emit undoChanged ();
01759 }
01760 //END
01761 
01762 //BEGIN KTextEditor::SearchInterface stuff
01763 
01764 KTextEditor::Range KateDocument::searchText (const KTextEditor::Range & inputRange, const QString &text, bool casesensitive, bool backwards)
01765 {
01766   FAST_DEBUG("KateDocument::searchText( " << inputRange.start().line() << ", "
01767     << inputRange.start().column() << ", " << text << ", " << backwards << " )");
01768   if (text.isEmpty() || !inputRange.isValid() || (inputRange.start() == inputRange.end()))
01769   {
01770     return KTextEditor::Range::invalid();
01771   }
01772 
01773   // split multi-line needle into single lines
01774   const QString sep("\n");
01775   const QStringList needleLines = text.split(sep);
01776   const int numNeedleLines = needleLines.count();
01777   FAST_DEBUG("searchText | needle has " << numNeedleLines << " lines");
01778 
01779   if (numNeedleLines > 1)
01780   {
01781     // multi-line plaintext search (both forwards or backwards)
01782     const int lastLine = inputRange.end().line();
01783 
01784     const int forMin   = inputRange.start().line(); // first line in range
01785     const int forMax   = lastLine + 1 - numNeedleLines; // last line in range
01786     const int forInit  = backwards ? forMax : forMin;
01787     const int forInc   = backwards ? -1 : +1;
01788 
01789     const int minLeft  = inputRange.start().column();
01790     const uint maxRight = inputRange.end().column(); // first not included
01791 
01792     // init hay line ring buffer
01793     int hayLinesZeroIndex = 0;
01794     QVector<KateTextLine::Ptr> hayLinesWindow;
01795     for (int i = 0; i < numNeedleLines; i++) {
01796       KateTextLine::Ptr textLine = m_buffer->plainLine((backwards ? forMax : forMin) + i);
01797 
01798       if (!textLine)
01799         return KTextEditor::Range::invalid();
01800 
01801       hayLinesWindow.append (textLine);
01802       FAST_DEBUG("searchText | hayLinesWindow[" << i << "] = \"" << hayLinesWindow[i]->string() << "\"");
01803     }
01804 
01805     for (int j = forInit; (forMin <= j) && (j <= forMax); j += forInc)
01806     {
01807       // try to match all lines
01808       uint startCol = 0; // init value not important
01809       for (int k = 0; k < numNeedleLines; k++)
01810       {
01811         // which lines to compare
01812         const QString & needleLine = needleLines[k];
01813         KateTextLine::Ptr & hayLine = hayLinesWindow[(k + hayLinesZeroIndex) % numNeedleLines];
01814         FAST_DEBUG("searchText | hayLine = \"" << hayLine->string() << "\"");
01815 
01816         // position specific comparison (first, middle, last)
01817         if (k == 0) {
01818           // first line
01819           if (needleLine.length() == 0) // if needle starts with a newline
01820           {
01821             startCol = hayLine->length();
01822           }
01823           else
01824           {
01825             uint myMatchLen;
01826             const uint colOffset = (j > forMin) ? 0 : minLeft;
01827             const bool matches = hayLine->searchText(colOffset, hayLine->length(),needleLine, &startCol,
01828               &myMatchLen, casesensitive, false);
01829             if (!matches || (startCol + myMatchLen != static_cast<uint>(hayLine->length()))) {
01830               FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": no");
01831               break;
01832             }
01833             FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": yes");
01834           }
01835         } else if (k == numNeedleLines - 1) {
01836           // last line
01837           uint foundAt, myMatchLen;
01838           const bool matches = hayLine->searchText(0,hayLine->length(), needleLine, &foundAt, &myMatchLen, casesensitive, false);
01839           if (matches && (foundAt == 0) && !((k == lastLine)
01840               && (static_cast<uint>(foundAt + myMatchLen) > maxRight))) // full match!
01841           {
01842             FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": yes");
01843             return KTextEditor::Range(j, startCol, j + k, needleLine.length());
01844           }
01845           FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": no");
01846         } else {
01847           // mid lines
01848           uint foundAt, myMatchLen;
01849           const bool matches = hayLine->searchText(0, hayLine->length(),needleLine, &foundAt, &myMatchLen, casesensitive, false);
01850           if (!matches || (foundAt != 0) || (myMatchLen != static_cast<uint>(needleLine.length()))) {
01851             FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": no");
01852             break;
01853           }
01854           FAST_DEBUG("searchText | [" << j << " + " << k << "] line " << j + k << ": yes");
01855         }
01856       }
01857 
01858       // get a fresh line into the ring buffer
01859       if ((backwards && (j > forMin)) || (!backwards && (j < forMax)))
01860       {
01861         if (backwards)
01862         {
01863           hayLinesZeroIndex = (hayLinesZeroIndex + numNeedleLines - 1) % numNeedleLines;
01864 
01865           KateTextLine::Ptr textLine = m_buffer->plainLine(j - 1);
01866 
01867           if (!textLine)
01868             return KTextEditor::Range::invalid();
01869 
01870           hayLinesWindow[hayLinesZeroIndex] = textLine;
01871 
01872           FAST_DEBUG("searchText | filling slot " << hayLinesZeroIndex << " with line "
01873             << j - 1 << ": " << hayLinesWindow[hayLinesZeroIndex]->string());
01874         }
01875         else
01876         {
01877           hayLinesWindow[hayLinesZeroIndex] = m_buffer->plainLine(j + numNeedleLines);
01878           FAST_DEBUG("searchText | filling slot " << hayLinesZeroIndex << " with line "
01879             << j + numNeedleLines << ": " << hayLinesWindow[hayLinesZeroIndex]->string());
01880           hayLinesZeroIndex = (hayLinesZeroIndex + 1) % numNeedleLines;
01881         }
01882       }
01883     }
01884 
01885     // not found
01886     return KTextEditor::Range::invalid();
01887   }
01888   else
01889   {
01890     // single-line plaintext search (both forward of backward mode)
01891     const int minLeft  = inputRange.start().column();
01892     const uint maxRight = inputRange.end().column(); // first not included
01893     const int forMin   = inputRange.start().line();
01894     const int forMax   = inputRange.end().line();
01895     const int forInit  = backwards ? forMax : forMin;
01896     const int forInc   = backwards ? -1 : +1;
01897     FAST_DEBUG("searchText | single line " << (backwards ? forMax : forMin) << ".."
01898       << (backwards ? forMin : forMax));
01899     for (int j = forInit; (forMin <= j) && (j <= forMax); j += forInc)
01900     {
01901       KateTextLine::Ptr textLine = m_buffer->plainLine(j);
01902       if (!textLine)
01903       {
01904         FAST_DEBUG("searchText | line " << j << ": no");
01905         return KTextEditor::Range::invalid();
01906       }
01907 
01908       const int offset = (j == forMin) ? minLeft : 0;
01909       const int line_end= (j==forMax) ? maxRight : textLine->length();
01910       uint foundAt, myMatchLen;
01911       FAST_DEBUG("searchText | searching in line line: " << j);
01912       const bool found = textLine->searchText (offset,line_end, text, &foundAt, &myMatchLen, casesensitive, backwards);
01913       if (found && !((j == forMax) && (static_cast<uint>(foundAt + myMatchLen) > maxRight)))
01914       {
01915         FAST_DEBUG("searchText | line " << j << ": yes");
01916         return KTextEditor::Range(j, foundAt, j, foundAt + myMatchLen);
01917       }
01918       else
01919       {
01920         FAST_DEBUG("searchText | line " << j << ": no");
01921       }
01922     }
01923   }
01924   return KTextEditor::Range::invalid();
01925 }
01926 
01927 
01928 
01929 // helper structs for captures re-construction
01930 struct TwoViewCursor {
01931   int index;
01932   int openLine;
01933   int openCol;
01934   int closeLine;
01935   int closeCol;
01936   // note: open/close distinction does not seem needed
01937   // anymore. i keep it to make a potential way back
01938   // easier. overhead is minimal.
01939 };
01940 
01941 struct IndexPair {
01942   int openIndex;
01943   int closeIndex;
01944 };
01945 
01946 
01947 
01948 QVector<KTextEditor::Range> KateDocument::searchRegex(
01949     const KTextEditor::Range & inputRange,
01950     QRegExp &regexp,
01951     bool backwards)
01952 {
01953   FAST_DEBUG("KateDocument::searchRegex( " << inputRange.start().line() << ", "
01954     << inputRange.start().column() << ", " << regexp.pattern() << ", " << backwards << " )");
01955   if (regexp.isEmpty() || !regexp.isValid() || !inputRange.isValid() || (inputRange.start() == inputRange.end()))
01956   {
01957     QVector<KTextEditor::Range> result;
01958     result.append(KTextEditor::Range::invalid());
01959     return result;
01960   }
01961 
01962 
01963   // detect pattern type (single- or mutli-line)
01964   bool isMultiLine;
01965   QString multiLinePattern = regexp.pattern();
01966 
01967   // detect '.' and '\s' and fix them
01968   const bool dotMatchesNewline = false; // TODO
01969   const int replacements = KateDocument::repairPattern(multiLinePattern, isMultiLine);
01970   if (dotMatchesNewline && (replacements > 0))
01971   {
01972     isMultiLine = true;
01973   }
01974 
01975   const int firstLineIndex = inputRange.start().line();
01976   const int minColStart = inputRange.start().column();
01977 //  const int maxColEnd = inputRange.end().column();
01978   if (isMultiLine)
01979   {
01980     // multi-line regex search (both forward and backward mode)
01981     QString wholeDocument;
01982     const int inputLineCount = inputRange.end().line() - inputRange.start().line() + 1;
01983     FAST_DEBUG("multi line search (lines " << firstLineIndex << ".." << firstLineIndex + inputLineCount - 1 << ")");
01984 
01985     // nothing to do...
01986     if (firstLineIndex >= m_buffer->lines())
01987     {
01988       QVector<KTextEditor::Range> result;
01989       result.append(KTextEditor::Range::invalid());
01990       return result;
01991     }
01992 
01993     QVector<int> lineLens (inputLineCount);
01994 
01995     // first line
01996     KateTextLine::Ptr firstLine = m_buffer->plainLine(firstLineIndex);
01997     if (!firstLine)
01998     {
01999       QVector<KTextEditor::Range> result;
02000       result.append(KTextEditor::Range::invalid());
02001       return result;
02002     }
02003 
02004     QString firstLineText = firstLine->string();
02005     const int firstLineLen = firstLineText.length() - minColStart;
02006     wholeDocument.append(firstLineText.right(firstLineLen));
02007     lineLens[0] = firstLineLen;
02008     FAST_DEBUG("  line" << 0 << "has length" << lineLens[0]);
02009 
02010     // second line and after
02011     const QString sep("\n");
02012     for (int i = 1; i < inputLineCount; i++)
02013     {
02014       KateTextLine::Ptr textLine = m_buffer->plainLine(firstLineIndex + i);
02015       if (!textLine)
02016       {
02017         QVector<KTextEditor::Range> result;
02018         result.append(KTextEditor::Range::invalid());
02019         return result;
02020       }
02021 
02022       QString text = textLine->string();
02023       lineLens[i] = text.length();
02024       wholeDocument.append(sep);
02025       wholeDocument.append(text);
02026       FAST_DEBUG("  line" << i << "has length" << lineLens[i]);
02027     }
02028 
02029     // apply modified pattern
02030     regexp.setPattern(multiLinePattern);
02031     const int pos = backwards
02032         ? KateDocument::fixedLastIndexIn(regexp, wholeDocument, -1, QRegExp::CaretAtZero)
02033         : regexp.indexIn(wholeDocument, 0, QRegExp::CaretAtZero);
02034     if (pos == -1)
02035     {
02036       // no match
02037       FAST_DEBUG("not found");
02038       {
02039         QVector<KTextEditor::Range> result;
02040         result.append(KTextEditor::Range::invalid());
02041         return result;
02042       }
02043     }
02044 
02045 #ifdef FAST_DEBUG_ENABLE
02046     const int matchLen = regexp.matchedLength();
02047     FAST_DEBUG("found at relative pos " << pos << ", length " << matchLen);
02048 #endif
02049 
02050     // save opening and closing indices and build a map.
02051     // the correct values will be written into it later.
02052     QMap<int, TwoViewCursor *> indicesToCursors;
02053     const int numCaptures = regexp.numCaptures();
02054     QVector<IndexPair> indexPairs(1 + numCaptures);
02055     for (int z = 0; z <= numCaptures; z++)
02056     {
02057       const int openIndex = regexp.pos(z);
02058       IndexPair & pair = indexPairs[z];
02059       if (openIndex == -1)
02060       {
02061         // empty capture gives invalid
02062         pair.openIndex = -1;
02063         pair.closeIndex = -1;
02064         FAST_DEBUG("capture []");
02065       }
02066       else
02067       {
02068         const int closeIndex = openIndex + regexp.cap(z).length();
02069         pair.openIndex = openIndex;
02070         pair.closeIndex = closeIndex;
02071         FAST_DEBUG("capture [" << pair.openIndex << ".." << pair.closeIndex << "]");
02072 
02073         // each key no more than once
02074         if (!indicesToCursors.contains(openIndex))
02075         {
02076           TwoViewCursor * twoViewCursor = new TwoViewCursor;
02077           twoViewCursor->index = openIndex;
02078           indicesToCursors.insert(openIndex, twoViewCursor);
02079           FAST_DEBUG("  border index added: " << openIndex);
02080         }
02081         if (!indicesToCursors.contains(closeIndex))
02082         {
02083           TwoViewCursor * twoViewCursor = new TwoViewCursor;
02084           twoViewCursor->index = closeIndex;
02085           indicesToCursors.insert(closeIndex, twoViewCursor);
02086           FAST_DEBUG("  border index added: " << closeIndex);
02087         }
02088       }
02089     }
02090 
02091     // find out where they belong
02092     int curRelLine = 0;
02093     int curRelCol = 0;
02094     int curRelIndex = 0;
02095     QMap<int, TwoViewCursor *>::const_iterator iter = indicesToCursors.constBegin();
02096     while (iter != indicesToCursors.end())
02097     {
02098       // forward to index, save line/col
02099       const int index = (*iter)->index;
02100       FAST_DEBUG("resolving position" << index);
02101       TwoViewCursor & twoViewCursor = *(*iter);
02102       while (curRelIndex <= index)
02103       {
02104         FAST_DEBUG("walk pos (" << curRelLine << "," << curRelCol << ") = "
02105             << curRelIndex << "relative, steps more to go" << index - curRelIndex);
02106         const int curRelLineLen = lineLens[curRelLine];
02107         const int curLineRemainder = curRelLineLen - curRelCol;
02108         const int lineFeedIndex = curRelIndex + curLineRemainder;
02109         if (index <= lineFeedIndex) {
02110             if (index == lineFeedIndex) {
02111                 // on this line _on_ line feed
02112                 FAST_DEBUG("  on line feed");
02113                 const int absLine = curRelLine + firstLineIndex;
02114                 twoViewCursor.openLine
02115                     = twoViewCursor.closeLine
02116                     = absLine;
02117                 twoViewCursor.openCol
02118                     = twoViewCursor.closeCol
02119                     = ((curRelLine == 0) ? minColStart : 0) + curRelLineLen;
02120 
02121                 // advance to next line
02122                 const int advance = (index - curRelIndex) + 1;
02123                 curRelLine++;
02124                 curRelCol = 0;
02125                 curRelIndex += advance;
02126             } else { // index < lineFeedIndex
02127                 // on this line _before_ line feed
02128                 FAST_DEBUG("  before line feed");
02129                 const int diff = (index - curRelIndex);
02130                 const int absLine = curRelLine + firstLineIndex;
02131                 const int absCol = ((curRelLine == 0) ? minColStart : 0) + curRelCol + diff;
02132                 twoViewCursor.openLine
02133                     = twoViewCursor.closeLine
02134                     = absLine;
02135                 twoViewCursor.openCol
02136                     = twoViewCursor.closeCol
02137                     = absCol;
02138 
02139                 // advance on same line
02140                 const int advance = diff + 1;
02141                 curRelCol += advance;
02142                 curRelIndex += advance;
02143             }
02144             FAST_DEBUG("open(" << twoViewCursor.openLine << "," << twoViewCursor.openCol
02145                 << ")  close(" << twoViewCursor.closeLine << "," << twoViewCursor.closeCol << ")");
02146         }
02147         else // if (index > lineFeedIndex)
02148         {
02149           // not on this line
02150           // advance to next line
02151           FAST_DEBUG("  not on this line");
02152           const int advance = curLineRemainder + 1;
02153           curRelLine++;
02154           curRelCol = 0;
02155           curRelIndex += advance;
02156         }
02157       }
02158 
02159       iter++;
02160     }
02161 
02162     // build result array
02163     QVector<KTextEditor::Range> result(1 + numCaptures);
02164     for (int y = 0; y <= numCaptures; y++)
02165     {
02166       IndexPair & pair = indexPairs[y];
02167       if ((pair.openIndex == -1) || (pair.closeIndex == -1))
02168       {
02169         result[y] = KTextEditor::Range::invalid();
02170       }
02171       else
02172       {
02173         const TwoViewCursor * const openCursors = indicesToCursors[pair.openIndex];
02174         const TwoViewCursor * const closeCursors = indicesToCursors[pair.closeIndex];
02175         const int startLine = openCursors->openLine;
02176         const int startCol = openCursors->openCol;
02177         const int endLine = closeCursors->closeLine;
02178         const int endCol = closeCursors->closeCol;
02179         FAST_DEBUG("range " << y << ": (" << startLine << ", " << startCol << ")..(" << endLine << ", " << endCol << ")");
02180         result[y] = KTextEditor::Range(startLine, startCol, endLine, endCol);
02181       }
02182     }
02183 
02184     // free structs allocated for indicesToCursors
02185     iter = indicesToCursors.constBegin();
02186     while (iter != indicesToCursors.end())
02187     {
02188       TwoViewCursor * const twoViewCursor = *iter;
02189       delete twoViewCursor;
02190       iter++;
02191     }
02192     return result;
02193   }
02194   else
02195   {
02196     // single-line regex search (both forward of backward mode)
02197     const int minLeft  = inputRange.start().column();
02198     const uint maxRight = inputRange.end().column(); // first not included
02199     const int forMin   = inputRange.start().line();
02200     const int forMax   = inputRange.end().line();
02201     const int forInit  = backwards ? forMax : forMin;
02202     const int forInc   = backwards ? -1 : +1;
02203     FAST_DEBUG("single line " << (backwards ? forMax : forMin) << ".."
02204       << (backwards ? forMin : forMax));
02205     for (int j = forInit; (forMin <= j) && (j <= forMax); j += forInc)
02206     {
02207       KateTextLine::Ptr textLine = m_buffer->plainLine(j);
02208       if (!textLine)
02209       {
02210         FAST_DEBUG("searchText | line " << j << ": no");
02211         QVector<KTextEditor::Range> result;
02212         result.append(KTextEditor::Range::invalid());
02213         return result;
02214       }
02215 
02216         // Find (and don't match ^ in between...)
02217         const int first = (j == forMin) ? minLeft : 0;
02218         const int afterLast = (j == forMax) ? maxRight : textLine->length();
02219         const QString hay = textLine->string();
02220         bool found = true;
02221         int foundAt;
02222         uint myMatchLen;
02223         if (backwards) {
02224             const int lineLen = textLine->length();
02225             const int offset = afterLast - lineLen - 1;
02226             FAST_DEBUG("lastIndexIn(" << hay << "," << offset << ")");
02227             foundAt = KateDocument::fixedLastIndexIn(regexp, hay, offset);
02228             found = (foundAt != -1) && (foundAt >= first);
02229         } else {
02230             FAST_DEBUG("indexIn(" << hay << "," << first << ")");
02231             foundAt = regexp.indexIn(hay, first);
02232             found = (foundAt != -1);
02233         }
02234         myMatchLen = found ? regexp.matchedLength() : 0;
02235 
02236       /*
02237       TODO do we still need this?
02238 
02239           // A special case which can only occur when searching with a regular expression consisting
02240           // only of a lookahead (e.g. ^(?=\{) for a function beginning without selecting '{').
02241           if (myMatchLen == 0 && line == startPosition.line() && foundAt == (uint) col)
02242           {
02243             if (col < lineLength(line))
02244               col++;
02245             else {
02246               line++;
02247               col = 0;
02248             }
02249             continue;
02250           }
02251       */
02252 
02253       if (found && !((j == forMax) && (static_cast<uint>(foundAt + myMatchLen) > maxRight)))
02254       {
02255         FAST_DEBUG("line " << j << ": yes");
02256 
02257         // build result array
02258         const int numCaptures = regexp.numCaptures();
02259         QVector<KTextEditor::Range> result(1 + numCaptures);
02260         result[0] = KTextEditor::Range(j, foundAt, j, foundAt + myMatchLen);
02261         FAST_DEBUG("result range " << 0 << ": (" << j << ", " << foundAt << ")..(" << j << ", " << foundAt + myMatchLen << ")");
02262         for (int y = 1; y <= numCaptures; y++)
02263         {
02264           const int openIndex = regexp.pos(y);
02265           if (openIndex == -1)
02266           {
02267             result[y] = KTextEditor::Range::invalid();
02268             FAST_DEBUG("capture []");
02269           }
02270           else
02271           {
02272             const int closeIndex = openIndex + regexp.cap(y).length();
02273             FAST_DEBUG("result range " << y << ": (" << j << ", " << openIndex << ")..(" << j << ", " << closeIndex << ")");
02274             result[y] = KTextEditor::Range(j, openIndex, j, closeIndex);
02275           }
02276         }
02277         return result;
02278       }
02279       else
02280       {
02281         FAST_DEBUG("searchText | line " << j << ": no");
02282       }
02283     }
02284   }
02285 
02286   QVector<KTextEditor::Range> result;
02287   result.append(KTextEditor::Range::invalid());
02288   return result;
02289 }
02290 
02291 QWidget * KateDocument::dialogParent()
02292 {
02293     QWidget *w=widget();
02294 
02295     if(!w)
02296     {
02297         w=activeView();
02298 
02299         if(!w)
02300             w=QApplication::activeWindow();
02301     }
02302 
02303     return w;
02304 }
02305 
02306 QVector<KTextEditor::Range> KateDocument::searchText(
02307     const KTextEditor::Range & range,
02308     const QString & pattern,
02309     const KTextEditor::Search::SearchOptions options)
02310 {
02311   // TODO
02312   // * support BlockInputRange
02313   // * support DotMatchesNewline
02314   QString workPattern(pattern);
02315 
02316   KTextEditor::Search::SearchOptions finalOptions(options);
02317   const bool escapeSequences = finalOptions.testFlag(KTextEditor::Search::EscapeSequences);
02318 
02319   // abuse regex for whole word plaintext search
02320   if (finalOptions.testFlag(KTextEditor::Search::WholeWords))
02321   {
02322     // resolve escape sequences like \t
02323     if (escapeSequences)
02324     {
02325       KateDocument::escapePlaintext(workPattern);
02326     }
02327 
02328     // escape dot and friends
02329     workPattern = "\\b" + QRegExp::escape(workPattern) + "\\b";
02330 
02331     // regex ON, whole words OFF
02332     finalOptions |= KTextEditor::Search::Regex;
02333     finalOptions &= ~KTextEditor::Search::SearchOptions(KTextEditor::Search::WholeWords);
02334   }
02335 
02336   const bool regexMode = finalOptions.testFlag(KTextEditor::Search::Regex);
02337   const bool caseSensitive = !finalOptions.testFlag(KTextEditor::Search::CaseInsensitive);
02338   const bool backwards = finalOptions.testFlag(KTextEditor::Search::Backwards);
02339 
02340   if (regexMode)
02341   {
02342     // regex search
02343     const Qt::CaseSensitivity caseSensitivity =
02344         caseSensitive
02345         ? Qt::CaseSensitive
02346         : Qt::CaseInsensitive;
02347 
02348     QRegExp matcher(workPattern, caseSensitivity);
02349     if (matcher.isValid())
02350     {
02351       // valid pattern
02352       // run engine
02353       return searchRegex(range, matcher, backwards);
02354     }
02355     else
02356     {
02357       // invalid pattern
02358       QVector<KTextEditor::Range> result;
02359       result.append(KTextEditor::Range::invalid());
02360       return result;
02361     }
02362   }
02363   else
02364   {
02365     // plaintext search
02366 
02367     // resolve escape sequences like \t
02368     if (escapeSequences)
02369     {
02370       KateDocument::escapePlaintext(workPattern);
02371     }
02372 
02373     // run engine
02374     KTextEditor::Range resultRange = searchText(range, workPattern, caseSensitive, backwards);
02375     QVector<KTextEditor::Range> result;
02376     result.append(resultRange);
02377     return result;
02378   }
02379 }
02380 
02381 
02382 
02383 KTextEditor::Search::SearchOptions KateDocument::supportedSearchOptions() const
02384 {
02385   KTextEditor::Search::SearchOptions supported(KTextEditor::Search::Default);
02386   supported |= KTextEditor::Search::Regex;
02387   supported |= KTextEditor::Search::CaseInsensitive;
02388   supported |= KTextEditor::Search::Backwards;
02389 // supported |= KTextEditor::Search::BlockInputRange;
02390   supported |= KTextEditor::Search::EscapeSequences;
02391   supported |= KTextEditor::Search::WholeWords;
02392 // supported |= KTextEditor::Search::DotMatchesNewline;
02393   return supported;
02394 }
02395 
02396 
02397 
02398 /*static*/ void KateDocument::escapePlaintext(QString & text, QList<ReplacementPart> * parts,
02399         bool replacementGoodies) {
02400   // get input
02401   const int inputLen = text.length();
02402   int input = 0; // walker index
02403 
02404   // prepare output
02405   QString output;
02406   output.reserve(inputLen + 1);
02407 
02408   while (input < inputLen)
02409   {
02410     switch (text[input].unicode())
02411     {
02412     case L'\n':
02413       output.append(text[input]);
02414       input++;
02415       break;
02416 
02417     case L'\\':
02418       if (input + 1 >= inputLen)
02419       {
02420         // copy backslash
02421         output.append(text[input]);
02422         input++;
02423         break;
02424       }
02425 
02426       switch (text[input + 1].unicode())
02427       {
02428       case L'0': // "\0000".."\0377"
02429         if (input + 4 >= inputLen)
02430         {
02431           if (parts == NULL)
02432           {
02433             // strip backslash ("\0" -> "0")
02434             output.append(text[input + 1]);
02435           }
02436           else
02437           {
02438             // handle reference
02439             ReplacementPart curPart;
02440 
02441             // append text before the reference
02442             if (!output.isEmpty())
02443             {
02444               curPart.type = ReplacementPart::Text;
02445               curPart.text = output;
02446               output.clear();
02447               parts->append(curPart);
02448               curPart.text.clear();
02449             }
02450 
02451             // append reference
02452             curPart.type = ReplacementPart::Reference;
02453             curPart.index = 0;
02454             parts->append(curPart);
02455           }
02456           input += 2;
02457         }
02458         else
02459         {
02460           bool stripAndSkip = false;
02461           const ushort text_2 = text[input + 2].unicode();
02462           if ((text_2 >= L'0') && (text_2 <= L'3'))
02463           {
02464             const ushort text_3 = text[input + 3].unicode();
02465             if ((text_3 >= L'0') && (text_3 <= L'7'))
02466             {
02467               const ushort text_4 = text[input + 4].unicode();
02468               if ((text_4 >= L'0') && (text_4 <= L'7'))
02469               {
02470                 int digits[3];
02471                 for (int i = 0; i < 3; i++)
02472                 {
02473                   digits[i] = 7 - (L'7' - text[input + 2 + i].unicode());
02474                 }
02475                 const int ch = 64 * digits[0] + 8 * digits[1] + digits[2];
02476                 output.append(QChar(ch));
02477                 input += 5;
02478               }
02479               else
02480               {
02481                 stripAndSkip = true;
02482               }
02483             }
02484             else
02485             {
02486               stripAndSkip = true;
02487             }
02488           }
02489           else
02490           {
02491             stripAndSkip = true;
02492           }
02493 
02494           if (stripAndSkip)
02495           {
02496             if (parts == NULL)
02497             {
02498               // strip backslash ("\0" -> "0")
02499               output.append(text[input + 1]);
02500             }
02501             else
02502             {
02503               // handle reference
02504               ReplacementPart curPart;
02505 
02506               // append text before the reference
02507               if (!output.isEmpty())
02508               {
02509                 curPart.type = ReplacementPart::Text;
02510                 curPart.text = output;
02511                 output.clear();
02512                 parts->append(curPart);
02513                 curPart.text.clear();
02514               }
02515 
02516               // append reference
02517               curPart.type = ReplacementPart::Reference;
02518               curPart.index = 0;
02519               parts->append(curPart);
02520             }
02521             input += 2;
02522           }
02523         }
02524         break;
02525 
02526       case L'1':
02527       case L'2':
02528       case L'3':
02529       case L'4':
02530       case L'5':
02531       case L'6':
02532       case L'7':
02533       case L'8':
02534       case L'9':
02535         if (parts == NULL)
02536         {
02537           // strip backslash ("\?" -> "?")
02538           output.append(text[input + 1]);
02539         }
02540         else
02541         {
02542           // handle reference
02543           ReplacementPart curPart;
02544 
02545           // append text before the reference
02546           if (!output.isEmpty())
02547           {
02548             curPart.type = ReplacementPart::Text;
02549             curPart.text = output;
02550             output.clear();
02551             parts->append(curPart);
02552             curPart.text.clear();
02553           }
02554 
02555           // append reference
02556           curPart.type = ReplacementPart::Reference;
02557           curPart.index = 9 - (L'9' - text[input + 1].unicode());
02558           parts->append(curPart);
02559         }
02560         input += 2;
02561         break;
02562 
02563       case L'E': // FALLTHROUGH
02564       case L'L': // FALLTHROUGH
02565       case L'U':
02566         if ((parts == NULL) || !replacementGoodies) {
02567           // strip backslash ("\?" -> "?")
02568           output.append(text[input + 1]);
02569         } else {
02570           // handle case switcher
02571           ReplacementPart curPart;
02572 
02573           // append text before case switcher
02574           if (!output.isEmpty())
02575           {
02576             curPart.type = ReplacementPart::Text;
02577             curPart.text = output;
02578             output.clear();
02579             parts->append(curPart);
02580             curPart.text.clear();
02581           }
02582 
02583           // append case switcher
02584           switch (text[input + 1].unicode()) {
02585           case L'L':
02586             curPart.type = ReplacementPart::LowerCase;
02587             break;
02588 
02589           case L'U':
02590             curPart.type = ReplacementPart::UpperCase;
02591             break;
02592 
02593           case L'E': // FALLTHROUGH
02594           default:
02595             curPart.type = ReplacementPart::KeepCase;
02596 
02597           }
02598           parts->append(curPart);
02599         }
02600         input += 2;
02601         break;
02602 
02603       case L'#':
02604         if ((parts == NULL) || !replacementGoodies) {
02605           // strip backslash ("\?" -> "?")
02606           output.append(text[input + 1]);
02607           input += 2;
02608         } else {
02609           // handle replacement counter
02610           ReplacementPart curPart;
02611 
02612           // append text before replacement counter
02613           if (!output.isEmpty())
02614           {
02615             curPart.type = ReplacementPart::Text;
02616             curPart.text = output;
02617             output.clear();
02618             parts->append(curPart);
02619             curPart.text.clear();
02620           }
02621 
02622           // eat and count all following hash marks
02623           // each hash stands for a leading zero: \### will produces 001, 002, ...
02624           int count = 1;
02625           while ((input + count + 1 < inputLen) && (text[input + count + 1].unicode() == L'#')) {
02626             count++;
02627           }
02628           curPart.type = ReplacementPart::Counter;
02629           curPart.index = count; // Each hash stands
02630           parts->append(curPart);
02631           input += 1 + count;
02632         }
02633         break;
02634 
02635       case L'a':
02636         output.append(QChar(0x07));
02637         input += 2;
02638         break;
02639 
02640       case L'f':
02641         output.append(QChar(0x0c));
02642         input += 2;
02643         break;
02644 
02645       case L'n':
02646         output.append(QChar(0x0a));
02647         input += 2;
02648         break;
02649 
02650       case L'r':
02651         output.append(QChar(0x0d));
02652         input += 2;
02653         break;
02654 
02655       case L't':
02656         output.append(QChar(0x09));
02657         input += 2;
02658         break;
02659 
02660       case L'v':
02661         output.append(QChar(0x0b));
02662         input += 2;
02663         break;
02664 
02665       case L'x': // "\x0000".."\xffff"
02666         if (input + 5 >= inputLen)
02667         {
02668           // strip backslash ("\x" -> "x")
02669           output.append(text[input + 1]);
02670           input += 2;
02671         }
02672         else
02673         {
02674           bool stripAndSkip = false;
02675           const ushort text_2 = text[input + 2].unicode();
02676           if (((text_2 >= L'0') && (text_2 <= L'9'))
02677               || ((text_2 >= L'a') && (text_2 <= L'f'))
02678               || ((text_2 >= L'A') && (text_2 <= L'F')))
02679           {
02680             const ushort text_3 = text[input + 3].unicode();
02681             if (((text_3 >= L'0') && (text_3 <= L'9'))
02682                 || ((text_3 >= L'a') && (text_3 <= L'f'))
02683                 || ((text_3 >= L'A') && (text_3 <= L'F')))
02684             {
02685               const ushort text_4 = text[input + 4].unicode();
02686               if (((text_4 >= L'0') && (text_4 <= L'9'))
02687                   || ((text_4 >= L'a') && (text_4 <= L'f'))
02688                   || ((text_4 >= L'A') && (text_4 <= L'F')))
02689               {
02690                 const ushort text_5 = text[input + 5].unicode();
02691                 if (((text_5 >= L'0') && (text_5 <= L'9'))
02692                     || ((text_5 >= L'a') && (text_5 <= L'f'))
02693                     || ((text_5 >= L'A') && (text_5 <= L'F')))
02694                 {
02695                   int digits[4];
02696                   for (int i = 0; i < 4; i++)
02697                   {
02698                     const ushort cur = text[input + 2 + i].unicode();
02699                     if ((cur >= L'0') && (cur <= L'9'))
02700                     {
02701                       digits[i] = 9 - (L'9' - cur);
02702                     }
02703                     else if ((cur >= L'a') && (cur <= L'f'))
02704                     {
02705                       digits[i] = 15 - (L'f' - cur);
02706                     }
02707                     else // if ((cur >= L'A') && (cur <= L'F')))
02708                     {
02709                       digits[i] = 15 - (L'F' - cur);
02710                     }
02711                   }
02712 
02713                   const int ch = 4096 * digits[0] + 256 * digits[1] + 16 * digits[2] + digits[3];
02714                   output.append(QChar(ch));
02715                   input += 6;
02716                 }
02717                 else
02718                 {
02719                   stripAndSkip = true;
02720                 }
02721               }
02722               else
02723               {
02724                 stripAndSkip = true;
02725               }
02726             }
02727             else
02728             {
02729               stripAndSkip = true;
02730             }
02731           }
02732 
02733           if (stripAndSkip)
02734           {
02735             // strip backslash ("\x" -> "x")
02736             output.append(text[input + 1]);
02737             input += 2;
02738           }
02739         }
02740         break;
02741 
02742       default:
02743         // strip backslash ("\?" -> "?")
02744         output.append(text[input + 1]);
02745         input += 2;
02746 
02747       }
02748       break;
02749 
02750     default:
02751       output.append(text[input]);
02752       input++;
02753 
02754     }
02755   }
02756 
02757   if (parts == NULL)
02758   {
02759     // overwrite with escaped edition
02760     text = output;
02761   }
02762   else
02763   {
02764     // append text after the last reference if any
02765     if (!output.isEmpty())
02766     {
02767       ReplacementPart curPart;
02768       curPart.type = ReplacementPart::Text;
02769       curPart.text = output;
02770       parts->append(curPart);
02771     }
02772   }
02773 }
02774 
02775 
02776 
02777 // these things can besides '.' and '\s' make apptern multi-line:
02778 // \n, \x000A, \x????-\x????, \0012, \0???-\0???
02779 // a multi-line pattern must not pass as single-line, the other
02780 // way around will just result in slower searches and is therefore
02781 // not as critical
02782 /*static*/ int KateDocument::repairPattern(QString & pattern, bool & stillMultiLine)
02783 {
02784   const QString & text = pattern; // read-only input for parsing
02785 
02786   // get input
02787   const int inputLen = text.length();
02788   int input = 0; // walker index
02789 
02790   // prepare output
02791   QString output;
02792   output.reserve(2 * inputLen + 1); // twice should be enough for the average case
02793 
02794   // parser state
02795   stillMultiLine = false;
02796   int replaceCount = 0;
02797   bool insideClass = false;
02798 
02799   while (input < inputLen)
02800   {
02801     if (insideClass)
02802     {
02803       // wait for closing, unescaped ']'
02804       switch (text[input].unicode())
02805       {
02806       case L'\\':
02807         switch (text[input + 1].unicode())
02808         {
02809         case L'x':
02810           if (input + 5 < inputLen)
02811           {
02812             // copy "\x????" unmodified
02813             output.append(text.mid(input, 6));
02814             input += 6;
02815           } else {
02816             // copy "\x" unmodified
02817             output.append(text.mid(input, 2));
02818             input += 2;
02819           }
02820           stillMultiLine = true;
02821           break;
02822 
02823         case L'0':
02824           if (input + 4 < inputLen)
02825           {
02826             // copy "\0???" unmodified
02827             output.append(text.mid(input, 5));
02828             input += 5;
02829           } else {
02830             // copy "\0" unmodified
02831             output.append(text.mid(input, 2));
02832             input += 2;
02833           }
02834           stillMultiLine = true;
02835           break;
02836 
02837         case L's':
02838           // replace "\s" with "[ \t]"
02839           output.append("[ \\t]");
02840           input += 2;
02841           replaceCount++;
02842           break;
02843 
02844         case L'n':
02845           stillMultiLine = true;
02846           // FALLTROUGH
02847 
02848         default:
02849           // copy "\?" unmodified
02850           output.append(text.mid(input, 2));
02851           input += 2;
02852         }
02853         break;
02854 
02855       case L']':
02856         // copy "]" unmodified
02857         insideClass = false;
02858         output.append(text[input]);
02859         input++;
02860         break;
02861 
02862       default:
02863         // copy "?" unmodified
02864         output.append(text[input]);
02865         input++;
02866 
02867       }
02868     }
02869     else
02870     {
02871       // search for real dots and \S
02872       switch (text[input].unicode())
02873       {
02874       case L'\\':
02875         switch (text[input + 1].unicode())
02876         {
02877         case L'x':
02878           if (input + 5 < inputLen)
02879           {
02880             // copy "\x????" unmodified
02881             output.append(text.mid(input, 6));
02882             input += 6;
02883           } else {
02884             // copy "\x" unmodified
02885             output.append(text.mid(input, 2));
02886             input += 2;
02887           }
02888           stillMultiLine = true;
02889           break;
02890 
02891         case L'0':
02892           if (input + 4 < inputLen)
02893           {
02894             // copy "\0???" unmodified
02895             output.append(text.mid(input, 5));
02896             input += 5;
02897           } else {
02898             // copy "\0" unmodified
02899             output.append(text.mid(input, 2));
02900             input += 2;
02901           }
02902           stillMultiLine = true;
02903           break;
02904 
02905         case L's':
02906           // replace "\s" with "[ \t]"
02907           output.append("[ \\t]");
02908           input += 2;
02909           replaceCount++;
02910           break;
02911 
02912         case L'n':
02913           stillMultiLine = true;
02914           // FALLTROUGH
02915 
02916         default:
02917           // copy "\?" unmodified
02918           output.append(text.mid(input, 2));
02919           input += 2;
02920         }
02921         break;
02922 
02923       case L'.':
02924         // replace " with "[^\n]"
02925         output.append("[^\\n]");
02926         input++;
02927         replaceCount++;
02928         break;
02929 
02930       case L'[':
02931         // copy "]" unmodified
02932         insideClass = true;
02933         output.append(text[input]);
02934         input++;
02935         break;
02936 
02937       default:
02938         // copy "?" unmodified
02939         output.append(text[input]);
02940         input++;
02941 
02942       }
02943     }
02944   }
02945 
02946   // Overwrite with repaired pattern
02947   pattern = output;
02948   return replaceCount;
02949 }
02950 
02951 
02952 
02953 /*static*/ int KateDocument::fixedLastIndexIn(const QRegExp & matcher, const QString & str,
02954         int offset, QRegExp::CaretMode caretMode) {
02955     int prevPos = -1;
02956     int prevLen = 1;
02957     const int strLen = str.length();
02958     for (;;) {
02959         const int pos = matcher.indexIn(str, prevPos + prevLen, caretMode);
02960         if (pos == -1) {
02961             // No more matches
02962             break;
02963         } else {
02964             const int len = matcher.matchedLength();
02965             if (pos > strLen + offset + 1) {
02966                 // Gone too far, match in no way of use
02967                 break;
02968             }
02969 
02970             if (pos + len > strLen + offset + 1) {
02971                 // Gone too far, check if usable
02972                 if (offset == -1) {
02973                     // No shrinking possible
02974                     break;
02975                 }
02976 
02977                 const QString str2 = str.mid(0, strLen + offset + 1);
02978                 const int pos2 = matcher.indexIn(str2, pos, caretMode);
02979                 if (pos2 != -1) {
02980                     // Match usable
02981                     return pos2;
02982                 } else {
02983                     // Match NOT usable
02984                     break;
02985                 }
02986             }
02987 
02988             // Valid match, but maybe not the last one
02989             prevPos = pos;
02990             prevLen = (len == 0) ? 1 : len;
02991         }
02992     }
02993 
02994     // Previous match is what we want
02995     if (prevPos != -1) {
02996         // Do that very search again
02997         matcher.indexIn(str, prevPos, caretMode);
02998         return prevPos;
02999     } else {
03000         return -1;
03001     }
03002 }
03003 //END
03004 
03005 //BEGIN KTextEditor::HighlightingInterface stuff
03006 bool KateDocument::setMode (const QString &name)
03007 {
03008   updateFileType (name);
03009   return true;
03010 }
03011 
03012 QString KateDocument::mode () const
03013 {
03014   return m_fileType;
03015 }
03016 
03017 QStringList KateDocument::modes () const
03018 {
03019   QStringList m;
03020 
03021   const QList<KateFileType *> &modeList = KateGlobal::self()->modeManager()->list();
03022   for (int i = 0; i < modeList.size(); ++i)
03023     m << modeList[i]->name;
03024 
03025   return m;
03026 }
03027 
03028 bool KateDocument::setHighlightingMode (const QString &name)
03029 {
03030   m_buffer->setHighlight (KateHlManager::self()->nameFind(name));
03031 
03032   if (true)
03033   {
03034     setDontChangeHlOnSave();
03035     return true;
03036   }
03037 
03038   return false;
03039 }
03040 
03041 QString KateDocument::highlightingMode () const
03042 {
03043   return highlight()->name ();
03044 }
03045 
03046 QStringList KateDocument::highlightingModes () const
03047 {
03048   QStringList hls;
03049 
03050   for (int i = 0; i < KateHlManager::self()->highlights(); ++i)
03051     hls << KateHlManager::self()->hlName (i);
03052 
03053   return hls;
03054 }
03055 
03056 QString KateDocument::highlightingModeSection( int index ) const
03057 {
03058   return KateHlManager::self()->hlSection( index );
03059 }
03060 
03061 QString KateDocument::modeSection( int index ) const
03062 {
03063   return KateGlobal::self()->modeManager()->list()[ index ]->section;
03064 }
03065 
03066 void KateDocument::bufferHlChanged ()
03067 {
03068   // update all views
03069   makeAttribs(false);
03070 
03071   // deactivate indenter if necessary
03072   m_indenter.checkRequiredStyle();
03073 
03074   emit highlightingModeChanged(this);
03075 }
03076 
03077 void KateDocument::setDontChangeHlOnSave()
03078 {
03079   hlSetByUser = true;
03080 }
03081 //END
03082 
03083 //BEGIN KTextEditor::ConfigInterface stuff
03084 void KateDocument::readSessionConfig(const KConfigGroup &kconfig)
03085 {
03086   // restore the url
03087   KUrl url (kconfig.readEntry("URL"));
03088 
03089   // get the encoding
03090   QString tmpenc=kconfig.readEntry("Encoding");
03091   if (!tmpenc.isEmpty() && (tmpenc != encoding()))
03092     setEncoding(tmpenc);
03093 
03094   // open the file if url valid
03095   if (!url.isEmpty() && url.isValid())
03096     openUrl (url);
03097   else completed(); //perhaps this should be emitted at the end of this function
03098 
03099   // restore the filetype
03100   updateFileType (kconfig.readEntry("Mode", "Normal"));
03101 
03102   // restore the hl stuff
03103   m_buffer->setHighlight(KateHlManager::self()->nameFind(kconfig.readEntry("Highlighting")));
03104 
03105   // indent mode
03106   config()->setIndentationMode( kconfig.readEntry("Indentation Mode", config()->indentationMode() ) );
03107 
03108   // Restore Bookmarks
03109   QList<int> marks = kconfig.readEntry("Bookmarks", QList<int>());
03110   for( int i = 0; i < marks.count(); i++ )
03111     addMark( marks[i], KateDocument::markType01 );
03112 }
03113 
03114 void KateDocument::writeSessionConfig(KConfigGroup &kconfig)
03115 {
03116   if ( this->url().isLocalFile() ) {
03117     const QString path = this->url().path();
03118     if ( KGlobal::dirs()->relativeLocation( "tmp", path ) != path ) {
03119       return; // inside tmp resource, do not save
03120     }
03121   }
03122   // save url
03123   kconfig.writeEntry("URL", this->url().prettyUrl() );
03124 
03125   // save encoding
03126   kconfig.writeEntry("Encoding",encoding());
03127 
03128   // save file type
03129   kconfig.writeEntry("Mode", m_fileType);
03130 
03131   // save hl
03132   kconfig.writeEntry("Highlighting", highlight()->name());
03133 
03134   // indent mode
03135   kconfig.writeEntry("Indentation Mode", config()->indentationMode() );
03136 
03137   // Save Bookmarks
03138   QList<int> marks;
03139   for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
03140     if (i.value()->type & KTextEditor::MarkInterface::markType01)
03141      marks << i.value()->line;
03142 
03143   kconfig.writeEntry( "Bookmarks", marks );
03144 }
03145 
03146 uint KateDocument::mark( int line )
03147 {
03148   if( !m_marks.value(line) )
03149     return 0;
03150 
03151   return m_marks[line]->type;
03152 }
03153 
03154 void KateDocument::setMark( int line, uint markType )
03155 {
03156   clearMark( line );
03157   addMark( line, markType );
03158 }
03159 
03160 void KateDocument::clearMark( int line )
03161 {
03162   if( line > lastLine() )
03163     return;
03164 
03165   if( !m_marks.value(line) )
03166     return;
03167 
03168   KTextEditor::Mark* mark = m_marks.take( line );
03169   emit markChanged( this, *mark, MarkRemoved );
03170   emit marksChanged( this );
03171   delete mark;
03172   tagLines( line, line );
03173   repaintViews(true);
03174 }
03175 
03176 void KateDocument::addMark( int line, uint markType )
03177 {
03178   if( line > lastLine())
03179     return;
03180 
03181   if( markType == 0 )
03182     return;
03183 
03184   if( m_marks.value(line) ) {
03185     KTextEditor::Mark* mark = m_marks[line];
03186 
03187     // Remove bits already set
03188     markType &= ~mark->type;
03189 
03190     if( markType == 0 )
03191       return;
03192 
03193     // Add bits
03194     mark->type |= markType;
03195   } else {
03196     KTextEditor::Mark *mark = new KTextEditor::Mark;
03197     mark->line = line;
03198     mark->type = markType;
03199     m_marks.insert( line, mark );
03200   }
03201 
03202   // Emit with a mark having only the types added.
03203   KTextEditor::Mark temp;
03204   temp.line = line;
03205   temp.type = markType;
03206   emit markChanged( this, temp, MarkAdded );
03207 
03208   emit marksChanged( this );
03209   tagLines( line, line );
03210   repaintViews(true);
03211 }
03212 
03213 void KateDocument::removeMark( int line, uint markType )
03214 {
03215   if( line > lastLine() )
03216     return;
03217 
03218   if( !m_marks.value(line) )
03219     return;
03220 
03221   KTextEditor::Mark* mark = m_marks[line];
03222 
03223   // Remove bits not set
03224   markType &= mark->type;
03225 
03226   if( markType == 0 )
03227     return;
03228 
03229   // Subtract bits
03230   mark->type &= ~markType;
03231 
03232   // Emit with a mark having only the types removed.
03233   KTextEditor::Mark temp;
03234   temp.line = line;
03235   temp.type = markType;
03236   emit markChanged( this, temp, MarkRemoved );
03237 
03238   if( mark->type == 0 )
03239     m_marks.remove( line );
03240 
03241   emit marksChanged( this );
03242   tagLines( line, line );
03243   repaintViews(true);
03244 }
03245 
03246 const QHash<int, KTextEditor::Mark*> &KateDocument::marks()
03247 {
03248   return m_marks;
03249 }
03250 
03251 void KateDocument::clearMarks()
03252 {
03253   while (!m_marks.isEmpty())
03254   {
03255     QHash<int, KTextEditor::Mark*>::iterator it = m_marks.begin();
03256     KTextEditor::Mark mark = *it.value();
03257     delete it.value();
03258     m_marks.erase (it);
03259 
03260     emit markChanged( this, mark, MarkRemoved );
03261     tagLines( mark.line, mark.line );
03262   }
03263 
03264   m_marks.clear();
03265 
03266   emit marksChanged( this );
03267   repaintViews(true);
03268 }
03269 
03270 void KateDocument::setMarkPixmap( MarkInterface::MarkTypes type, const QPixmap& pixmap )
03271 {
03272   m_markPixmaps.insert( type, pixmap );
03273 }
03274 
03275 void KateDocument::setMarkDescription( MarkInterface::MarkTypes type, const QString& description )
03276 {
03277   m_markDescriptions.insert( type, description );
03278 }
03279 
03280 QPixmap KateDocument::markPixmap( MarkInterface::MarkTypes type ) const
03281 {
03282   return m_markPixmaps.contains(type) ?
03283          m_markPixmaps[type] : QPixmap();
03284 }
03285 
03286 QColor KateDocument::markColor( MarkInterface::MarkTypes type ) const
03287 {
03288   uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1;
03289   if ((uint)type >= (uint)markType01 && (uint)type <= reserved) {
03290     return KateRendererConfig::global()->lineMarkerColor(type);
03291   } else {
03292     return QColor();
03293   }
03294 }
03295 
03296 QString KateDocument::markDescription( MarkInterface::MarkTypes type ) const
03297 {
03298   return m_markDescriptions.contains(type) ?
03299          m_markDescriptions[type] : QString();
03300 }
03301 
03302 void KateDocument::setEditableMarks( uint markMask )
03303 {
03304   m_editableMarks = markMask;
03305 }
03306 
03307 uint KateDocument::editableMarks() const
03308 {
03309   return m_editableMarks;
03310 }
03311 //END
03312 
03313 //BEGIN KTextEditor::PrintInterface stuff
03314 bool KateDocument::printDialog ()
03315 {
03316   return KatePrinter::print (this);
03317 }
03318 
03319 bool KateDocument::print ()
03320 {
03321   return KatePrinter::print (this);
03322 }
03323 //END
03324 
03325 //BEGIN KTextEditor::DocumentInfoInterface (### unfinished)
03326 QString KateDocument::mimeType()
03327 {
03328   KMimeType::Ptr result = KMimeType::defaultMimeTypePtr();
03329 
03330   // if the document has a URL, try KMimeType::findByURL
03331   if ( ! this->url().isEmpty() )
03332     result = KMimeType::findByUrl( this->url() );
03333 
03334   else if ( this->url().isEmpty() || ! this->url().isLocalFile() )
03335     result = mimeTypeForContent();
03336 
03337   return result->name();
03338 }
03339 
03340 KMimeType::Ptr KateDocument::mimeTypeForContent()
03341 {
03342   QByteArray buf (1024,'\0');
03343   uint bufpos = 0;
03344 
03345   for (int i=0; i < lines(); ++i)
03346   {
03347     QString line = this->line( i );
03348     uint len = line.length() + 1;
03349 
03350     if (bufpos + len > 1024)
03351       len = 1024 - bufpos;
03352 
03353     QString ld (line + QChar::fromAscii('\n'));
03354     buf.replace(bufpos,len,ld.toLatin1()); //memcpy(buf.data() + bufpos, ld.toLatin1().constData(), len);
03355 
03356     bufpos += len;
03357 
03358     if (bufpos >= 1024)
03359       break;
03360   }
03361   buf.resize( bufpos );
03362 
03363   int accuracy = 0;
03364   KMimeType::Ptr mt = KMimeType::findByContent(buf, &accuracy);
03365   return mt ? mt : KMimeType::defaultMimeTypePtr();
03366 }
03367 //END KTextEditor::DocumentInfoInterface
03368 
03369 
03370 //BEGIN KParts::ReadWrite stuff
03371 bool KateDocument::openFile()
03372 {
03373   // no open errors until now...
03374   setOpeningError(false);
03375 
03376   // add new m_file to dirwatch
03377   activateDirWatch ();
03378 
03379   //
03380   // mime type magic to get encoding right
03381   //
03382   QString mimeType = arguments().mimeType();
03383   int pos = mimeType.indexOf(';');
03384   if (pos != -1)
03385     setEncoding (mimeType.mid(pos+1));
03386 
03387   // do we have success ?
03388   emit KTextEditor::Document::textRemoved(this, documentRange());
03389   history()->doEdit( new KateEditInfo(this, Kate::CloseFileEdit, documentRange(), QStringList(), KTextEditor::Range(0,0,0,0), QStringList()) );
03390 
03391   bool success = m_buffer->openFile (localFilePath());
03392 
03393   //
03394   // yeah, success
03395   //
03396   if (success)
03397   {
03398     emit KTextEditor::Document::textInserted(this, documentRange());
03399     history()->doEdit( new KateEditInfo(this, Kate::OpenFileEdit, KTextEditor::Range(0,0,0,0), QStringList(), documentRange(), QStringList()) );
03400 
03401     // update file type
03402     updateFileType (KateGlobal::self()->modeManager()->fileType (this));
03403 
03404     // read dir config (if possible and wanted)
03405     readDirConfig ();
03406 
03407     // read vars
03408     readVariables();
03409 
03410     // update the md5 digest
03411     createDigest( m_digest );
03412 
03413     if (!m_postLoadFilterChecks.isEmpty())
03414     {
03415       LoadSaveFilterCheckPlugins *lscps=loadSaveFilterCheckPlugins();
03416       foreach(const QString& checkplugin, m_postLoadFilterChecks)
03417       {
03418          lscps->postLoadFilter(checkplugin,this);
03419       }
03420     }
03421   }
03422 
03423   // Inform that the text has changed (required as we're not inside the usual editStart/End stuff)
03424   emit textChanged (this);
03425 
03426   //
03427   // update views
03428   //
03429   foreach (KateView * view, m_views)
03430   {
03431     // This is needed here because inserting the text moves the view's start position (it is a SmartCursor)
03432     view->setCursorPosition(KTextEditor::Cursor());
03433     view->updateView(true);
03434   }
03435 
03436   //
03437   // emit the signal we need for example for kate app
03438   //
03439   emit documentUrlChanged (this);
03440 
03441   //
03442   // set doc name, dummy value as arg, don't need it
03443   //
03444   setDocName  (QString());
03445 
03446   //
03447   // to houston, we are not modified
03448   //
03449   if (m_modOnHd)
03450   {
03451     m_modOnHd = false;
03452     m_modOnHdReason = OnDiskUnmodified;
03453     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
03454   }
03455 
03456   //
03457   // display errors
03458   //
03459   QWidget *parentWidget(dialogParent());
03460 
03461   if (!suppressOpeningErrorDialogs())
03462   {
03463     if (!success)
03464       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()));
03465   }
03466 
03467   if (!success) {
03468     setOpeningError(true);
03469     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()));
03470   }
03471 
03472   // warn -> opened binary file!!!!!!!
03473   if (m_buffer->binary())
03474   {
03475     // this file can't be saved again without killing it
03476     setReadWrite( false );
03477 
03478     if(!suppressOpeningErrorDialogs())
03479       KMessageBox::information (parentWidget
03480         , i18n ("The file %1 is a binary, saving it will result in a corrupt file.", this->url().pathOrUrl())
03481         , i18n ("Binary File Opened")
03482         , "Binary File Opened Warning");
03483 
03484     setOpeningError(true);
03485     setOpeningErrorMessage(i18n ("The file %1 is a binary, saving it will result in a corrupt file.", this->url().pathOrUrl()));
03486   }
03487 
03488   // warn: opened broken utf-8 file...
03489   // only warn if not already a binary file!
03490   else if (m_buffer->brokenUTF8())
03491   {
03492     // this file can't be saved again without killing it
03493     setReadWrite( false );
03494 
03495     if (!suppressOpeningErrorDialogs())
03496       KMessageBox::information (parentWidget
03497         , i18n ("The file %1 was opened with UTF-8 encoding but contained invalid characters."
03498                 " It is set to read-only mode, as saving might destroy its content."
03499                 " 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())
03500         , i18n ("Broken UTF-8 File Opened")
03501         , "Broken UTF-8 File Opened Warning");
03502     setOpeningError(true);
03503     setOpeningErrorMessage(i18n ("The file %1 was opened with UTF-8 encoding but contained invalid characters."
03504               " It is set to read-only mode, as saving might destroy its content."
03505               " 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()));
03506   }
03507 
03508   //
03509   // return the success
03510   //
03511   return success;
03512 }
03513 
03514 bool KateDocument::saveFile()
03515 {
03516   QWidget *parentWidget(dialogParent());
03517 
03518   //
03519   // warn -> try to save binary file!!!!!!!
03520   //
03521   if (m_buffer->binary() && (KMessageBox::warningContinueCancel (parentWidget
03522         , i18n ("The file %1 is a binary, saving it will result in a corrupt file.", url().pathOrUrl())
03523         , i18n ("Trying to Save Binary File")
03524         , KGuiItem(i18n("Save Nevertheless"))
03525         , KStandardGuiItem::cancel(), "Binary File Save Warning") != KMessageBox::Continue))
03526     return false;
03527 
03528   // some warnings, if file was changed by the outside!
03529   if ( !url().isEmpty() )
03530   {
03531     if (s_fileChangedDialogsActivated && m_modOnHd)
03532     {
03533       QString str = reasonedMOHString() + "\n\n";
03534 
03535       if (!isModified())
03536       {
03537         if (KMessageBox::warningContinueCancel(parentWidget,
03538                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)
03539           return false;
03540       }
03541       else
03542       {
03543         if (KMessageBox::warningContinueCancel(parentWidget,
03544                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)
03545           return false;
03546       }
03547     }
03548   }
03549 
03550   //
03551   // can we encode it if we want to save it ?
03552   //
03553   if (!m_buffer->canEncode ()
03554        && (KMessageBox::warningContinueCancel(parentWidget,
03555            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))
03556   {
03557     return false;
03558   }
03559 
03560   //
03561   // try to create backup file..
03562   //
03563 
03564   // local file or not is here the question
03565   bool l ( url().isLocalFile() );
03566 
03567   // does the user want any backup, if not, not our problem?
03568   if ( ( l && config()->backupFlags() & KateDocumentConfig::LocalFiles )
03569        || ( ! l && config()->backupFlags() & KateDocumentConfig::RemoteFiles ) )
03570   {
03571     KUrl u( url() );
03572     u.setFileName( config()->backupPrefix() + url().fileName() + config()->backupSuffix() );
03573 
03574     kDebug( 13020 ) << "backup src file name: " << url();
03575     kDebug( 13020 ) << "backup dst file name: " << u;
03576 
03577     // handle the backup...
03578     bool backupSuccess = false;
03579 
03580     // local file mode, no kio
03581     if (u.isLocalFile ())
03582     {
03583       if (QFile::exists (url().toLocalFile ()))
03584       {
03585         // first: check if backupFile is already there, if true, unlink it
03586         QFile backupFile (u.toLocalFile ());
03587         if (backupFile.exists()) backupFile.remove ();
03588 
03589         backupSuccess = QFile::copy (url().toLocalFile (), u.toLocalFile ());
03590       }
03591       else
03592         backupSuccess = true;
03593     }
03594     else // remote file mode, kio
03595     {
03596       QWidget *w = widget ();
03597       if (!w && !m_views.isEmpty ())
03598         w = m_views.first();
03599 
03600       // get the right permissions, start with safe default
03601       mode_t  perms = 0600;
03602       KIO::UDSEntry fentry;
03603       if (KIO::NetAccess::stat (url(), fentry, kapp->activeWindow()))
03604       {
03605         kDebug( 13020 ) << "stating succesfull: " << url();
03606         KFileItem item (fentry, url());
03607         perms = item.permissions();
03608 
03609         // do a evil copy which will overwrite target if possible
03610         KIO::FileCopyJob *job = KIO::file_copy ( url(), u, -1, KIO::Overwrite );
03611         backupSuccess = KIO::NetAccess::synchronousRun(job, w);
03612       }
03613       else
03614         backupSuccess = true;
03615     }
03616 
03617     // backup has failed, ask user how to proceed
03618     if (!backupSuccess && (KMessageBox::warningContinueCancel (parentWidget
03619         , i18n ("For file %1 no backup copy could be created before saving."
03620                 " If an error occurs while saving, you might lose the data of this file."
03621                 " 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())
03622         , i18n ("Failed to create backup copy.")
03623         , KGuiItem(i18n("Try to Save Nevertheless"))
03624         , KStandardGuiItem::cancel(), "Backup Failed Warning") != KMessageBox::Continue))
03625     {
03626       return false;
03627     }
03628   }
03629 
03630   if (!m_preSavePostDialogFilterChecks.isEmpty())
03631   {
03632     LoadSaveFilterCheckPlugins *lscps=loadSaveFilterCheckPlugins();
03633     foreach(const QString& checkplugin, m_preSavePostDialogFilterChecks)
03634     {
03635        if (lscps->preSavePostDialogFilterCheck(checkplugin,this)==false)
03636          return false;
03637     }
03638   }
03639 
03640   // remember the oldpath...
03641   QString oldPath = m_dirWatchFile;
03642 
03643   // remove file from dirwatch
03644   deactivateDirWatch ();
03645 
03646   //
03647   // try to save
03648   //
03649   if (!m_buffer->saveFile (localFilePath()))
03650   {
03651     // add m_file again to dirwatch
03652     activateDirWatch (oldPath);
03653 
03654     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()));
03655 
03656     return false;
03657   }
03658 
03659   // update the md5 digest
03660   createDigest( m_digest );
03661 
03662   // add m_file again to dirwatch
03663   activateDirWatch ();
03664 
03665   // update file type
03666   updateFileType (KateGlobal::self()->modeManager()->fileType (this));
03667 
03668   // read dir config (if possible and wanted)
03669   if ( url().isLocalFile())
03670   {
03671     QFileInfo fo (oldPath), fn (m_dirWatchFile);
03672 
03673     if (fo.path() != fn.path())
03674       readDirConfig();
03675   }
03676 
03677   // read our vars
03678   readVariables();
03679 
03680   //
03681   // we are not modified
03682   //
03683   if (m_modOnHd)
03684   {
03685     m_modOnHd = false;
03686     m_modOnHdReason = OnDiskUnmodified;
03687     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
03688   }
03689 
03690   // update document name...
03691   setDocName( QString() );
03692 
03693   // url may have changed...
03694   emit documentUrlChanged (this);
03695 
03696   //
03697   // return success
03698   //
03699   return true;
03700 }
03701 
03702 void KateDocument::readDirConfig ()
03703 {
03704   int depth = config()->searchDirConfigDepth ();
03705 
03706   if (this->url().isLocalFile() && (depth > -1))
03707   {
03708     QString currentDir = QFileInfo (localFilePath()).absolutePath();
03709 
03710     // only search as deep as specified or not at all ;)
03711     while (depth > -1)
03712     {
03713       //kDebug (13020) << "search for config file in path: " << currentDir;
03714 
03715       // try to open config file in this dir
03716       QFile f (currentDir + "/.kateconfig");
03717 
03718       if (f.open (QIODevice::ReadOnly))
03719       {
03720         QTextStream stream (&f);
03721 
03722         uint linesRead = 0;
03723         QString line = stream.readLine();
03724         while ((linesRead < 32) && !line.isNull())
03725         {
03726           readVariableLine( line );
03727 
03728           line = stream.readLine();
03729 
03730           linesRead++;
03731         }
03732 
03733         break;
03734       }
03735 
03736       QString newDir = QFileInfo (currentDir).absolutePath();
03737 
03738       // bail out on looping (for example reached /)
03739       if (currentDir == newDir)
03740         break;
03741 
03742       currentDir = newDir;
03743       --depth;
03744     }
03745   }
03746 }
03747 
03748 void KateDocument::activateDirWatch (const QString &useFileName)
03749 {
03750   QString fileToUse = useFileName;
03751   if (fileToUse.isEmpty())
03752     fileToUse = localFilePath();
03753 
03754   // same file as we are monitoring, return
03755   if (fileToUse == m_dirWatchFile)
03756     return;
03757 
03758   // remove the old watched file
03759   deactivateDirWatch ();
03760 
03761   // add new file if needed
03762   if (url().isLocalFile() && !fileToUse.isEmpty())
03763   {
03764     KateGlobal::self()->dirWatch ()->addFile (fileToUse);
03765     m_dirWatchFile = fileToUse;
03766   }
03767 }
03768 
03769 void KateDocument::deactivateDirWatch ()
03770 {
03771   if (!m_dirWatchFile.isEmpty())
03772     KateGlobal::self()->dirWatch ()->removeFile (m_dirWatchFile);
03773 
03774   m_dirWatchFile.clear();
03775 }
03776 
03777 bool KateDocument::closeUrl()
03778 {
03779   //
03780   // file mod on hd
03781   //
03782   if ( !m_reloading && !url().isEmpty() )
03783   {
03784     if (s_fileChangedDialogsActivated && m_modOnHd)
03785     {
03786       QWidget *parentWidget(dialogParent());
03787 
03788       if (!(KMessageBox::warningContinueCancel(
03789             parentWidget,
03790             reasonedMOHString() + "\n\n" + i18n("Do you really want to continue to close this file? Data loss may occur."),
03791             i18n("Possible Data Loss"), KGuiItem(i18n("Close Nevertheless")), KStandardGuiItem::cancel(),
03792             QString("kate_close_modonhd_%1").arg( m_modOnHdReason ) ) == KMessageBox::Continue))
03793         return false;
03794     }
03795   }
03796 
03797   //
03798   // first call the normal kparts implementation
03799   //
03800   if (!KParts::ReadWritePart::closeUrl ())
03801     return false;
03802 
03803   // Tell the world that we're about to go ahead with the close
03804   if (!m_reloading)
03805     emit aboutToClose(this);
03806 
03807   // remove file from dirwatch
03808   deactivateDirWatch ();
03809 
03810   //
03811   // empty url + fileName
03812   //
03813   setUrl(KUrl());
03814   setLocalFilePath(QString());
03815 
03816   // we are not modified
03817   if (m_modOnHd)
03818   {
03819     m_modOnHd = false;
03820     m_modOnHdReason = OnDiskUnmodified;
03821     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
03822   }
03823 
03824   emit KTextEditor::Document::textRemoved(this, documentRange());
03825   history()->doEdit( new KateEditInfo(this, Kate::CloseFileEdit, documentRange(), QStringList(), KTextEditor::Range(0,0,0,0), QStringList()) );
03826 
03827   // clear the buffer
03828   m_buffer->clear();
03829 
03830   // remove all marks
03831   clearMarks ();
03832 
03833   // clear undo/redo history
03834   clearUndo();
03835   clearRedo();
03836 
03837   // no, we are no longer modified
03838   setModified(false);
03839 
03840   // we have no longer any hl
03841   m_buffer->setHighlight(0);
03842 
03843   // update all our views
03844   foreach (KateView * view, m_views )
03845   {
03846     view->clearSelection(); // fix bug #118588
03847     view->clear();
03848   }
03849 
03850   // uh, fileName changed
03851   emit documentUrlChanged (this);
03852 
03853   // update doc name
03854   setDocName (QString());
03855 
03856   // success
03857   return true;
03858 }
03859 
03860 void KateDocument::setReadWrite( bool rw )
03861 {
03862   if (isReadWrite() != rw)
03863   {
03864     KParts::ReadWritePart::setReadWrite (rw);
03865 
03866     foreach( KateView* view, m_views)
03867     {
03868       view->slotUpdate();
03869       view->slotReadWriteChanged ();
03870     }
03871   }
03872 }
03873 
03874 void KateDocument::setModified(bool m) {
03875 
03876   if (isModified() != m) {
03877     KParts::ReadWritePart::setModified (m);
03878 
03879     foreach( KateView* view,m_views)
03880     {
03881       view->slotUpdate();
03882     }
03883 
03884     emit modifiedChanged (this);
03885   }
03886   if ( m == false )
03887   {
03888     if ( ! undoItems.isEmpty() )
03889     {
03890       lastUndoGroupWhenSaved = undoItems.last();
03891     }
03892 
03893     if ( ! redoItems.isEmpty() )
03894     {
03895       lastRedoGroupWhenSaved = redoItems.last();
03896     }
03897 
03898     docWasSavedWhenUndoWasEmpty = undoItems.isEmpty();
03899     docWasSavedWhenRedoWasEmpty = redoItems.isEmpty();
03900   }
03901 }
03902 //END
03903 
03904 //BEGIN Kate specific stuff ;)
03905 
03906 void KateDocument::makeAttribs(bool needInvalidate)
03907 {
03908   foreach(KateView *view,m_views)
03909     view->renderer()->updateAttributes ();
03910 
03911   if (needInvalidate)
03912     m_buffer->invalidateHighlighting();
03913 
03914   tagAll ();
03915 }
03916 
03917 // the attributes of a hl have changed, update
03918 void KateDocument::internalHlChanged()
03919 {
03920   makeAttribs();
03921 }
03922 
03923 void KateDocument::addView(KTextEditor::View *view) {
03924   if (!view)
03925     return;
03926 
03927   m_views.append( static_cast<KateView*>(view) );
03928   m_textEditViews.append( view );
03929 
03930   // apply the view & renderer vars from the file type
03931   if (!m_fileType.isEmpty())
03932       readVariableLine(KateGlobal::self()->modeManager()->fileType(m_fileType).varLine, true);
03933 
03934   // apply the view & renderer vars from the file
03935   readVariables (true);
03936 
03937   setActiveView(view);
03938 }
03939 
03940 void KateDocument::removeView(KTextEditor::View *view) {
03941   if (!view)
03942     return;
03943 
03944   if (activeView() == view)
03945     setActiveView(0L);
03946 
03947   m_views.removeAll( (KateView *) view );
03948   m_textEditViews.removeAll( view  );
03949 }
03950 
03951 void KateDocument::setActiveView(KTextEditor::View* view)
03952 {
03953   if ( m_activeView == view ) return;
03954 
03955   if (m_activeView) {
03956     disconnect(m_activeView, SIGNAL(selectionChanged(KTextEditor::View*)), this, SIGNAL(activeViewSelectionChanged(KTextEditor::View*)));
03957   }
03958 
03959   m_activeView = (KateView*)view;
03960 
03961   if (m_activeView) {
03962     connect(m_activeView, SIGNAL(selectionChanged(KTextEditor::View*)), SIGNAL(activeViewSelectionChanged(KTextEditor::View*)));
03963   }
03964 }
03965 
03966 bool KateDocument::ownedView(KateView *view) {
03967   // do we own the given view?
03968   return (m_views.contains(view));
03969 }
03970 
03971 uint KateDocument::toVirtualColumn( const KTextEditor::Cursor& cursor )
03972 {
03973   KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line());
03974 
03975   if (textLine)
03976     return textLine->toVirtualColumn(cursor.column(), config()->tabWidth());
03977   else
03978     return 0;
03979 }
03980 
03981 bool KateDocument::typeChars ( KateView *view, const QString &chars )
03982 {
03983   // Because we want to access text before starting an edit, lock the smart mutex now
03984   QMutexLocker l(smartMutex());
03985 
03986   KateTextLine::Ptr textLine = m_buffer->plainLine(view->cursorPosition().line ());
03987 
03988   if (!textLine)
03989     return false;
03990 
03991   bool bracketInserted = false;
03992   QString buf;
03993   QChar c;
03994   for( int z = 0; z < chars.length(); z++ )
03995   {
03996     QChar ch = c = chars[z];
03997 
03998     if (ch.isPrint() || ch == QChar::fromAscii('\t'))
03999     {
04000       buf.append (ch);
04001 
04002       if (!bracketInserted && (config()->configFlags() & KateDocumentConfig::cfAutoBrackets))
04003       {
04004         QChar end_ch;
04005         bool complete = true;
04006         QChar prevChar = textLine->at(view->cursorPosition().column()-1);
04007         QChar nextChar = textLine->at(view->cursorPosition().column());
04008         switch(ch.toAscii()) {
04009           case '(': end_ch = ')'; break;
04010           case '[': end_ch = ']'; break;
04011           case '{': end_ch = '}'; break;
04012           case '\'':end_ch = '\'';break;
04013           case '"': end_ch = '"'; break;
04014           default: complete = false;
04015         }
04016         if (complete)
04017         {
04018           if (view->selection())
04019           { // there is a selection, enclose the selection
04020             buf.append (view->selectionText());
04021             buf.append (end_ch);
04022             bracketInserted = true;
04023           }
04024           else
04025           { // no selection, check whether we should better refuse to complete
04026             if ( ( (ch == '\'' || ch == '"') &&
04027                    (prevChar.isLetterOrNumber() || prevChar == ch) )
04028               || nextChar.isLetterOrNumber()
04029               || (nextChar == end_ch && prevChar != ch) )
04030             {
04031               kDebug(13020) << "AutoBracket refused before: " << nextChar << "\n";
04032             }
04033             else
04034             {
04035               buf.append (end_ch);
04036               bracketInserted = true;
04037             }
04038           }
04039         }
04040       }
04041     }
04042   }
04043 
04044   if (buf.isEmpty())
04045     return false;
04046 
04047   l.unlock(); //editStart will lock the smart-mutex again, and it must be un-locked within editEnd. So unlock here.
04048 
04049   editStart ();
04050 
04051   if (!view->config()->persistentSelection() && view->selection() )
04052     view->removeSelectedText();
04053 
04054   KTextEditor::Cursor oldCur (view->cursorPosition());
04055 
04056   if (config()->configFlags()  & KateDocumentConfig::cfOvr)
04057     removeText(KTextEditor::Range(view->cursorPosition(), qMin(buf.length(), textLine->length() - view->cursorPosition().column())));
04058 
04059   insertText(view->cursorPosition(), buf);
04060   KTextEditor::Cursor b(view->cursorPosition());
04061   m_indenter.userTypedChar (view, b, c);
04062 
04063   editEnd ();
04064 
04065   if (bracketInserted)
04066     view->setCursorPositionInternal (view->cursorPosition() - KTextEditor::Cursor(0,1));
04067 
04068   view->slotTextInserted (view, oldCur, chars);
04069   return true;
04070 }
04071 
04072 void KateDocument::newLine( KateView *v )
04073 {
04074   editStart();
04075 
04076   if( !v->config()->persistentSelection() && v->selection() )
04077     v->removeSelectedText();
04078 
04079   // query cursor position
04080   KTextEditor::Cursor c = v->cursorPosition();
04081 
04082   if (c.line() > (int)lastLine())
04083     c.setLine(lastLine());
04084 
04085   if (c.line() < 0)
04086     c.setLine(0);
04087 
04088   uint ln = c.line();
04089 
04090   KateTextLine::Ptr textLine = plainKateTextLine(ln);
04091 
04092   if (c.column() > (int)textLine->length())
04093     c.setColumn(textLine->length());
04094 
04095   // first: wrap line
04096   editWrapLine (c.line(), c.column());
04097 
04098   // second: indent the new line, if needed...
04099   m_indenter.userTypedChar(v, v->cursorPosition(), '\n');
04100 
04101   removeTrailingSpace( ln );
04102 
04103   editEnd();
04104 }
04105 
04106 void KateDocument::transpose( const KTextEditor::Cursor& cursor)
04107 {
04108   KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line());
04109 
04110   if (!textLine || (textLine->length() < 2))
04111     return;
04112 
04113   uint col = cursor.column();
04114 
04115   if (col > 0)
04116     col--;
04117 
04118   if ((textLine->length() - col) < 2)
04119     return;
04120 
04121   uint line = cursor.line();
04122   QString s;
04123 
04124   //clever swap code if first character on the line swap right&left
04125   //otherwise left & right
04126   s.append (textLine->at(col+1));
04127   s.append (textLine->at(col));
04128   //do the swap
04129 
04130   // do it right, never ever manipulate a textline
04131   editStart ();
04132   editRemoveText (line, col, 2);
04133   editInsertText (line, col, s);
04134   editEnd ();
04135 }
04136 
04137 void KateDocument::backspace( KateView *view, const KTextEditor::Cursor& c )
04138 {
04139   if ( !view->config()->persistentSelection() && view->selection() ) {
04140     view->removeSelectedText();
04141     return;
04142   }
04143 
04144   uint col = qMax( c.column(), 0 );
04145   uint line = qMax( c.line(), 0 );
04146 
04147   if ((col == 0) && (line == 0))
04148     return;
04149 
04150   int complement = 0;
04151   if (col > 0)
04152   {
04153     if (config()->configFlags() & KateDocumentConfig::cfAutoBrackets)
04154     {
04155       // if inside empty (), {}, [], '', "" delete both
04156       KateTextLine::Ptr tl = m_buffer->plainLine(line);
04157       if(!tl) return;
04158       QChar prevChar = tl->at(col-1);
04159       QChar nextChar = tl->at(col);
04160 
04161       if ( (prevChar == '"' && nextChar == '"') ||
04162            (prevChar == '\'' && nextChar == '\'') ||
04163            (prevChar == '(' && nextChar == ')') ||
04164            (prevChar == '[' && nextChar == ']') ||
04165            (prevChar == '{' && nextChar == '}') )
04166       {
04167         complement = 1;
04168       }
04169     }
04170     if (!(config()->configFlags() & KateDocumentConfig::cfBackspaceIndents))
04171     {
04172       // ordinary backspace
04173       //c.cursor.col--;
04174       removeText(KTextEditor::Range(line, col-1, line, col+complement));
04175     }
04176     else
04177     {
04178       // backspace indents: erase to next indent position
04179       KateTextLine::Ptr textLine = m_buffer->plainLine(line);
04180 
04181       // don't forget this check!!!! really!!!!
04182       if (!textLine)
04183         return;
04184 
04185       int colX = textLine->toVirtualColumn(col, config()->tabWidth());
04186       int pos = textLine->firstChar();
04187       if (pos > 0)
04188         pos = textLine->toVirtualColumn(pos, config()->tabWidth());
04189 
04190       if (pos < 0 || pos >= (int)colX)
04191       {
04192         // only spaces on left side of cursor
04193         indent( view, line, -1);
04194       }
04195       else
04196         removeText(KTextEditor::Range(line, col-1, line, col+complement));
04197     }
04198   }
04199   else
04200   {
04201     // col == 0: wrap to previous line
04202     if (line >= 1)
04203     {
04204       KateTextLine::Ptr textLine = m_buffer->plainLine(line-1);
04205 
04206       // don't forget this check!!!! really!!!!
04207       if (!textLine)
04208         return;
04209 
04210       if (config()->wordWrap() && textLine->endsWith(QLatin1String(" ")))
04211       {
04212         // gg: in hard wordwrap mode, backspace must also eat the trailing space
04213         removeText (KTextEditor::Range(line-1, textLine->length()-1, line, 0));
04214       }
04215       else
04216         removeText (KTextEditor::Range(line-1, textLine->length(), line, 0));
04217     }
04218   }
04219 }
04220 
04221 void KateDocument::del( KateView *view, const KTextEditor::Cursor& c )
04222 {
04223   if ( !view->config()->persistentSelection() && view->selection() ) {
04224     view->removeSelectedText();
04225     return;
04226   }
04227 
04228   if( c.column() < (int) m_buffer->plainLine(c.line())->length())
04229   {
04230     removeText(KTextEditor::Range(c, 1));
04231   }
04232   else if ( c.line() < lastLine() )
04233   {
04234     removeText(KTextEditor::Range(c.line(), c.column(), c.line()+1, 0));
04235   }
04236 }
04237 
04238 void KateDocument::paste ( KateView* view, QClipboard::Mode mode )
04239 {
04240   QString s = QApplication::clipboard()->text(mode);
04241 
04242   if (s.isEmpty())
04243     return;
04244 
04245   int lines = s.count (QChar::fromAscii ('\n'));
04246 
04247   m_undoDontMerge = true;
04248 
04249   editStart (true, Kate::CutCopyPasteEdit);
04250 
04251   if (!view->config()->persistentSelection() && view->selection() )
04252     view->removeSelectedText();
04253 
04254   KTextEditor::Cursor pos = view->cursorPosition();
04255 
04256   blockRemoveTrailingSpaces(true);
04257   insertText(pos, s, view->blockSelectionMode());
04258   blockRemoveTrailingSpaces(false);
04259 
04260   for (int i = pos.line(); i < pos.line() + lines; ++i)
04261     removeTrailingSpace(i);
04262 
04263   editEnd();
04264 
04265   // move cursor right for block select, as the user is moved right internal
04266   // even in that case, but user expects other behavior in block selection
04267   // mode !
04268   if (view->blockSelectionMode())
04269     view->setCursorPositionInternal(pos + KTextEditor::Cursor(lines, 0));
04270 
04271   if (config()->configFlags() & KateDocumentConfig::cfIndentPastedText)
04272   {
04273     KTextEditor::Range range = KTextEditor::Range(KTextEditor::Cursor(pos.line(), 0),
04274                                                   KTextEditor::Cursor(pos.line() + lines, 0));
04275 
04276     int start = view->selectionRange().start().line();
04277     const int end = view->selectionRange().end().line();
04278 
04279     editStart();
04280 
04281     blockRemoveTrailingSpaces(true);
04282     m_indenter.indent(view, range);
04283     blockRemoveTrailingSpaces(false);
04284 
04285     for (; start <= end; ++start)
04286       removeTrailingSpace(start);
04287 
04288     editEnd();
04289   }
04290 
04291   if (!view->blockSelectionMode()) emit charactersSemiInteractivelyInserted (pos, s);
04292   m_undoDontMerge = true;
04293 }
04294 
04295 void KateDocument::indent ( KateView *v, uint line, int change)
04296 {
04297   // dominik: if there is a selection, iterate afterwards over all lines and
04298   // remove trailing spaces
04299   const bool hasSelection = v->selection();
04300   int start = v->selectionRange().start().line();
04301   const int end = v->selectionRange().end().line();
04302 
04303   KTextEditor::Range range = hasSelection ? v->selectionRange() : KTextEditor::Range (KTextEditor::Cursor (line,0), KTextEditor::Cursor (line,0));
04304 
04305   editStart();
04306   blockRemoveTrailingSpaces(true);
04307   m_indenter.changeIndent(v, range, change);
04308   blockRemoveTrailingSpaces(false);
04309 
04310   if (hasSelection) {
04311     for (; start <= end; ++start)
04312       removeTrailingSpace(start);
04313   }
04314   editEnd();
04315 }
04316 
04317 void KateDocument::align(KateView *view, uint line)
04318 {
04319   const bool hasSelection = view->selection();
04320   KTextEditor::Range range = hasSelection ? view->selectionRange() : KTextEditor::Range (KTextEditor::Cursor (line,0), KTextEditor::Cursor (line,0));
04321 
04322   int start = view->selectionRange().start().line();
04323   const int end = view->selectionRange().end().line();
04324 
04325   blockRemoveTrailingSpaces(true);
04326   m_indenter.indent(view,range);
04327   blockRemoveTrailingSpaces(false);
04328 
04329   for (; start <= end; ++start)
04330     removeTrailingSpace(start);
04331 
04332   editEnd();
04333 }
04334 
04335 /*
04336   Remove a given string at the beginning
04337   of the current line.
04338 */
04339 bool KateDocument::removeStringFromBeginning(int line, const QString &str)
04340 {
04341   KateTextLine::Ptr textline = m_buffer->plainLine(line);
04342 
04343   KTextEditor::Cursor cursor (line, 0);
04344   bool there = textline->startsWith(str);
04345 
04346   if (!there)
04347   {
04348     cursor.setColumn(textline->firstChar());
04349     there = textline->matchesAt(cursor.column(), str);
04350   }
04351 
04352   if (there)
04353   {
04354     // Remove some chars
04355     removeText (KTextEditor::Range(cursor, str.length()));
04356   }
04357 
04358   return there;
04359 }
04360 
04361 /*
04362   Remove a given string at the end
04363   of the current line.
04364 */
04365 bool KateDocument::removeStringFromEnd(int line, const QString &str)
04366 {
04367   KateTextLine::Ptr textline = m_buffer->plainLine(line);
04368 
04369   KTextEditor::Cursor cursor (line, 0);
04370   bool there = textline->endsWith(str);
04371 
04372   if (there)
04373   {
04374     cursor.setColumn(textline->length() - str.length());
04375   }
04376   else
04377   {
04378     cursor.setColumn(textline->lastChar() - str.length() + 1);
04379     there = textline->matchesAt(cursor.column(), str);
04380   }
04381 
04382   if (there)
04383   {
04384     // Remove some chars
04385     removeText (KTextEditor::Range(cursor, str.length()));
04386   }
04387 
04388   return there;
04389 }
04390 
04391 /*
04392   Add to the current line a comment line mark at the beginning.
04393 */
04394 void KateDocument::addStartLineCommentToSingleLine( int line, int attrib )
04395 {
04396   QString commentLineMark = highlight()->getCommentSingleLineStart(attrib);
04397   int pos = -1;
04398 
04399   if (highlight()->getCommentSingleLinePosition(attrib) == KateHighlighting::CSLPosColumn0)
04400   {
04401     pos = 0;
04402     commentLineMark += ' ';
04403   } else {
04404     const KateTextLine::Ptr l = m_buffer->line(line);
04405     pos = l->firstChar();
04406   }
04407 
04408   if (pos >= 0)
04409     insertText (KTextEditor::Cursor(line, pos), commentLineMark);
04410 }
04411 
04412 /*
04413   Remove from the current line a comment line mark at
04414   the beginning if there is one.
04415 */
04416 bool KateDocument::removeStartLineCommentFromSingleLine( int line, int attrib )
04417 {
04418   const QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib );
04419   const QString longCommentMark = shortCommentMark + ' ';
04420 
04421   editStart();
04422 
04423   // Try to remove the long comment mark first
04424   bool removed = (removeStringFromBeginning(line, longCommentMark)
04425                || removeStringFromBeginning(line, shortCommentMark));
04426 
04427   editEnd();
04428 
04429   return removed;
04430 }
04431 
04432 /*
04433   Add to the current line a start comment mark at the
04434   beginning and a stop comment mark at the end.
04435 */
04436 void KateDocument::addStartStopCommentToSingleLine( int line, int attrib )
04437 {
04438   const QString startCommentMark = highlight()->getCommentStart( attrib ) + ' ';
04439   const QString stopCommentMark = ' ' + highlight()->getCommentEnd( attrib );
04440 
04441   editStart();
04442 
04443   // Add the start comment mark
04444   insertText (KTextEditor::Cursor(line, 0), startCommentMark);
04445 
04446   // Go to the end of the line
04447   const int col = m_buffer->plainLine(line)->length();
04448 
04449   // Add the stop comment mark
04450   insertText (KTextEditor::Cursor(line, col), stopCommentMark);
04451 
04452   editEnd();
04453 }
04454 
04455 /*
04456   Remove from the current line a start comment mark at
04457   the beginning and a stop comment mark at the end.
04458 */
04459 bool KateDocument::removeStartStopCommentFromSingleLine( int line, int attrib )
04460 {
04461   QString shortStartCommentMark = highlight()->getCommentStart( attrib );
04462   QString longStartCommentMark = shortStartCommentMark + ' ';
04463   QString shortStopCommentMark = highlight()->getCommentEnd( attrib );
04464   QString longStopCommentMark = ' ' + shortStopCommentMark;
04465 
04466   editStart();
04467 
04468 #ifdef __GNUC__
04469 #warning "that's a bad idea, can lead to stray endings, FIXME"
04470 #endif
04471   // Try to remove the long start comment mark first
04472   bool removedStart = (removeStringFromBeginning(line, longStartCommentMark)
04473                     || removeStringFromBeginning(line, shortStartCommentMark));
04474 
04475   bool removedStop = false;
04476   if (removedStart)
04477   {
04478     // Try to remove the long stop comment mark first
04479     removedStop = (removeStringFromEnd(line, longStopCommentMark)
04480                 || removeStringFromEnd(line, shortStopCommentMark));
04481   }
04482 
04483   editEnd();
04484 
04485   return (removedStart || removedStop);
04486 }
04487 
04488 /*
04489   Add to the current selection a start comment mark at the beginning
04490   and a stop comment mark at the end.
04491 */
04492 void KateDocument::addStartStopCommentToSelection( KateView *view, int attrib )
04493 {
04494   const QString startComment = highlight()->getCommentStart( attrib );
04495   const QString endComment = highlight()->getCommentEnd( attrib );
04496 
04497   KTextEditor::Range range = view->selectionRange();
04498 
04499   if ((range.end().column() == 0) && (range.end().line() > 0))
04500     range.end().setPosition(range.end().line() - 1, lineLength(range.end().line() - 1));
04501 
04502   editStart();
04503 
04504   insertText (range.end(), endComment);
04505   insertText (range.start(), startComment);
04506 
04507   editEnd ();
04508   // selection automatically updated (KateSmartRange)
04509 }
04510 
04511 /*
04512   Add to the current selection a comment line mark at the beginning of each line.
04513 */
04514 void KateDocument::addStartLineCommentToSelection( KateView *view, int attrib )
04515 {
04516   const QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + ' ';
04517 
04518   int sl = view->selectionRange().start().line();
04519   int el = view->selectionRange().end().line();
04520 
04521   // if end of selection is in column 0 in last line, omit the last line
04522   if ((view->selectionRange().end().column() == 0) && (el > 0))
04523   {
04524     el--;
04525   }
04526 
04527   editStart();
04528 
04529   // For each line of the selection
04530   for (int z = el; z >= sl; z--) {
04531     //insertText (z, 0, commentLineMark);
04532     addStartLineCommentToSingleLine(z, attrib );
04533   }
04534 
04535   editEnd ();
04536   // selection automatically updated (KateSmartRange)
04537 }
04538 
04539 bool KateDocument::nextNonSpaceCharPos(int &line, int &col)
04540 {
04541   for(; line < (int)m_buffer->count(); line++) {
04542     KateTextLine::Ptr textLine = m_buffer->plainLine(line);
04543 
04544     if (!textLine)
04545       break;
04546 
04547     col = textLine->nextNonSpaceChar(col);
04548     if(col != -1)
04549       return true; // Next non-space char found
04550     col = 0;
04551   }
04552   // No non-space char found
04553   line = -1;
04554   col = -1;
04555   return false;
04556 }
04557 
04558 bool KateDocument::previousNonSpaceCharPos(int &line, int &col)
04559 {
04560   while(true)
04561   {
04562     KateTextLine::Ptr textLine = m_buffer->plainLine(line);
04563 
04564     if (!textLine)
04565       break;
04566 
04567     col = textLine->previousNonSpaceChar(col);
04568     if(col != -1) return true;
04569     if(line == 0) return false;
04570     --line;
04571     col = textLine->length();
04572   }
04573   // No non-space char found
04574   line = -1;
04575   col = -1;
04576   return false;
04577 }
04578 
04579 /*
04580   Remove from the selection a start comment mark at
04581   the beginning and a stop comment mark at the end.
04582 */
04583 bool KateDocument::removeStartStopCommentFromSelection( KateView *view, int attrib )
04584 {
04585   const QString startComment = highlight()->getCommentStart( attrib );
04586   const QString endComment = highlight()->getCommentEnd( attrib );
04587 
04588   int sl = qMax<int> (0, view->selectionRange().start().line());
04589   int el = qMin<int>  (view->selectionRange().end().line(), lastLine());
04590   int sc = view->selectionRange().start().column();
04591   int ec = view->selectionRange().end().column();
04592 
04593   // The selection ends on the char before selectEnd
04594   if (ec != 0) {
04595     --ec;
04596   } else if (el > 0) {
04597     --el;
04598     ec = m_buffer->plainLine(el)->length() - 1;
04599   }
04600 
04601   const int startCommentLen = startComment.length();
04602   const int endCommentLen = endComment.length();
04603 
04604   // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$2/
04605 
04606   bool remove = nextNonSpaceCharPos(sl, sc)
04607       && m_buffer->plainLine(sl)->matchesAt(sc, startComment)
04608       && previousNonSpaceCharPos(el, ec)
04609       && ( (ec - endCommentLen + 1) >= 0 )
04610       && m_buffer->plainLine(el)->matchesAt(ec - endCommentLen + 1, endComment);
04611 
04612   if (remove) {
04613     editStart();
04614 
04615     removeText (KTextEditor::Range(el, ec - endCommentLen + 1, el, ec + 1));
04616     removeText (KTextEditor::Range(sl, sc, sl, sc + startCommentLen));
04617 
04618     editEnd ();
04619     // selection automatically updated (KateSmartRange)
04620   }
04621 
04622   return remove;
04623 }
04624 
04625 bool KateDocument::removeStartStopCommentFromRegion(const KTextEditor::Cursor &start,const KTextEditor::Cursor &end,int attrib)
04626 {
04627   const QString startComment = highlight()->getCommentStart( attrib );
04628   const QString endComment = highlight()->getCommentEnd( attrib );
04629   const int startCommentLen = startComment.length();
04630   const int endCommentLen = endComment.length();
04631 
04632   const bool remove = m_buffer->plainLine(start.line())->matchesAt(start.column(), startComment)
04633                    && m_buffer->plainLine(end.line())->matchesAt(end.column() - endCommentLen , endComment);
04634   if (remove) {
04635     editStart();
04636       removeText(KTextEditor::Range(end.line(), end.column() - endCommentLen, end.line(), end.column()));
04637       removeText(KTextEditor::Range(start, startCommentLen));
04638     editEnd();
04639   }
04640   return remove;
04641 }
04642 
04643 /*
04644   Remove from the beginning of each line of the
04645   selection a start comment line mark.
04646 */
04647 bool KateDocument::removeStartLineCommentFromSelection( KateView *view, int attrib )
04648 {
04649   const QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib );
04650   const QString longCommentMark = shortCommentMark + ' ';
04651 
04652   int sl = view->selectionRange().start().line();
04653   int el = view->selectionRange().end().line();
04654 
04655   if ((view->selectionRange().end().column() == 0) && (el > 0))
04656   {
04657     el--;
04658   }
04659 
04660   bool removed = false;
04661 
04662   editStart();
04663 
04664   // For each line of the selection
04665   for (int z = el; z >= sl; z--)
04666   {
04667     // Try to remove the long comment mark first
04668     removed = (removeStringFromBeginning(z, longCommentMark)
04669             || removeStringFromBeginning(z, shortCommentMark)
04670             || removed);
04671   }
04672 
04673   editEnd();
04674   // selection automatically updated (KateSmartRange)
04675 
04676   return removed;
04677 }
04678 
04679 /*
04680   Comment or uncomment the selection or the current
04681   line if there is no selection.
04682 */
04683 void KateDocument::comment( KateView *v, uint line,uint column, int change)
04684 {
04685   // We need to check that we can sanely comment the selectino or region.
04686   // It is if the attribute of the first and last character of the range to
04687   // comment belongs to the same language definition.
04688   // for lines with no text, we need the attribute for the lines context.
04689   bool hassel = v->selection();
04690   int startAttrib, endAttrib;
04691   if ( hassel )
04692   {
04693     KateTextLine::Ptr ln = kateTextLine( v->selectionRange().start().line() );
04694     int l = v->selectionRange().start().line(), c = v->selectionRange().start().column();
04695     startAttrib = nextNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0;
04696 
04697     ln = kateTextLine( v->selectionRange().end().line() );
04698     l = v->selectionRange().end().line(), c = v->selectionRange().end().column();
04699     endAttrib = previousNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0;
04700   }
04701   else
04702   {
04703     KateTextLine::Ptr ln = kateTextLine( line );
04704     if ( ln->length() )
04705     {
04706       startAttrib = ln->attribute( ln->firstChar() );
04707       endAttrib = ln->attribute( ln->lastChar() );
04708     }
04709     else
04710     {
04711       int l = line, c = 0;
04712       if ( nextNonSpaceCharPos( l, c )  || previousNonSpaceCharPos( l, c ) )
04713         startAttrib = endAttrib = kateTextLine( l )->attribute( c );
04714       else
04715         startAttrib = endAttrib = 0;
04716     }
04717   }
04718 
04719   if ( ! highlight()->canComment( startAttrib, endAttrib ) )
04720   {
04721     kDebug(13020)<<"canComment( "<<startAttrib<<", "<<endAttrib<<" ) returned false!";
04722     return;
04723   }
04724 
04725   bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart( startAttrib ).isEmpty());
04726   bool hasStartStopCommentMark = ( !(highlight()->getCommentStart( startAttrib ).isEmpty())
04727       && !(highlight()->getCommentEnd( endAttrib ).isEmpty()) );
04728 
04729   bool removed = false;
04730 
04731   if (change > 0) // comment
04732   {
04733     if ( !hassel )
04734     {
04735       if ( hasStartLineCommentMark )
04736         addStartLineCommentToSingleLine( line, startAttrib );
04737       else if ( hasStartStopCommentMark )
04738         addStartStopCommentToSingleLine( line, startAttrib );
04739     }
04740     else
04741     {
04742       // anders: prefer single line comment to avoid nesting probs
04743       // If the selection starts after first char in the first line
04744       // or ends before the last char of the last line, we may use
04745       // multiline comment markers.
04746       // TODO We should try to detect nesting.
04747       //    - if selection ends at col 0, most likely she wanted that
04748       // line ignored
04749       if ( hasStartStopCommentMark &&
04750            ( !hasStartLineCommentMark || (
04751            ( v->selectionRange().start().column() > m_buffer->plainLine( v->selectionRange().start().line() )->firstChar() ) ||
04752            ( v->selectionRange().end().column() < ((int)m_buffer->plainLine( v->selectionRange().end().line() )->length()) )
04753          ) ) )
04754         addStartStopCommentToSelection( v, startAttrib );
04755       else if ( hasStartLineCommentMark )
04756         addStartLineCommentToSelection( v, startAttrib );
04757     }
04758   }
04759   else // uncomment
04760   {
04761     if ( !hassel )
04762     {
04763       removed = ( hasStartLineCommentMark
04764                   && removeStartLineCommentFromSingleLine( line, startAttrib ) )
04765         || ( hasStartStopCommentMark
04766              && removeStartStopCommentFromSingleLine( line, startAttrib ) );
04767       if ((!removed) && foldingTree()) {
04768         kDebug(13020)<<"easy approach for uncommenting did not work, trying harder (folding tree)";
04769         int commentRegion=(highlight()->commentRegion(startAttrib));
04770         if (commentRegion){
04771            KateCodeFoldingNode *n=foldingTree()->findNodeForPosition(line,column);
04772            if (n) {
04773             KTextEditor::Cursor start,end;
04774             if ((n->nodeType()==(int)commentRegion) && n->getBegin(foldingTree(), &start) && n->getEnd(foldingTree(), &end)) {
04775                 kDebug(13020)<<"Enclosing region found:"<<start.column()<<"/"<<start.line()<<"-"<<end.column()<<"/"<<end.line();
04776                 removeStartStopCommentFromRegion(start,end,startAttrib);
04777              } else {
04778                   kDebug(13020)<<"Enclosing region found, but not valid";
04779                   kDebug(13020)<<"Region found: "<<n->nodeType()<<" region needed: "<<commentRegion;
04780              }
04781             //perhaps nested regions should be hadled here too...
04782           } else kDebug(13020)<<"No enclosing region found";
04783         } else kDebug(13020)<<"No comment region specified for current hl";
04784       }
04785     }
04786     else
04787     {
04788       // anders: this seems like it will work with above changes :)
04789       removed = ( hasStartLineCommentMark
04790           && removeStartLineCommentFromSelection( v, startAttrib ) )
04791         || ( hasStartStopCommentMark
04792           && removeStartStopCommentFromSelection( v, startAttrib ) );
04793     }
04794   }
04795 }
04796 
04797 void KateDocument::transform( KateView *v, const KTextEditor::Cursor &c,
04798                             KateDocument::TextTransform t )
04799 {
04800   editStart();
04801   KTextEditor::Cursor cursor = c;
04802 
04803   if ( v->selection() )
04804   {
04805     // cache the selection and cursor, so we can be sure to restore.
04806     KTextEditor::Range selection = v->selectionRange();
04807 
04808     KTextEditor::Range range(selection.start(), 0);
04809     while ( range.start().line() <= selection.end().line() )
04810     {
04811       int start = 0;
04812       int end = lineLength( range.start().line() );
04813 
04814       if (range.start().line() == selection.start().line() || v->blockSelectionMode())
04815         start = selection.start().column();
04816 
04817       if (range.start().line() == selection.end().line() || v->blockSelectionMode())
04818         end = selection.end().column();
04819 
04820       if ( start > end )
04821       {
04822         int swapCol = start;
04823         start = end;
04824         end = swapCol;
04825       }
04826       range.start().setColumn( start );
04827       range.end().setColumn( end );
04828 
04829       QString s = text( range );
04830       QString old = s;
04831 
04832       if ( t == Uppercase )
04833         s = s.toUpper();
04834       else if ( t == Lowercase )
04835         s = s.toLower();
04836       else // Capitalize
04837       {
04838         KateTextLine::Ptr l = m_buffer->plainLine( range.start().line() );
04839         int p ( 0 );
04840         while( p < s.length() )
04841         {
04842           // If bol or the character before is not in a word, up this one:
04843           // 1. if both start and p is 0, upper char.
04844           // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper
04845           // 3. if p-1 is not in a word, upper.
04846           if ( ( ! range.start().column() && ! p ) ||
04847                    ( ( range.start().line() == selection.start().line() || v->blockSelectionMode() ) &&
04848                    ! p && ! highlight()->isInWord( l->at( range.start().column() - 1 )) ) ||
04849                    ( p && ! highlight()->isInWord( s.at( p-1 ) ) )
04850              )
04851             s[p] = s.at(p).toUpper();
04852           p++;
04853         }
04854       }
04855 
04856       if ( s != old )
04857       {
04858         removeText( range );
04859         insertText( range.start(), s );
04860       }
04861 
04862       range.setBothLines(range.start().line() + 1);
04863     }
04864 
04865     // restore selection
04866     v->setSelection( selection );
04867 
04868   } else {  // no selection
04869     QString old = text( KTextEditor::Range(cursor, 1) );
04870     QString s;
04871     switch ( t ) {
04872       case Uppercase:
04873       s = old.toUpper();
04874       break;
04875       case Lowercase:
04876       s = old.toLower();
04877       break;
04878       case Capitalize:
04879       {
04880         KateTextLine::Ptr l = m_buffer->plainLine( cursor.line() );
04881         while ( cursor.column() > 0 && highlight()->isInWord( l->at( cursor.column() - 1 ), l->attribute( cursor.column() - 1 ) ) )
04882           cursor.setColumn(cursor.column() - 1);
04883         old = text( KTextEditor::Range(cursor, 1) );
04884         s = old.toUpper();
04885       }
04886       break;
04887       default:
04888       break;
04889     }
04890     if ( s != old )
04891     {
04892       removeText( KTextEditor::Range(cursor, 1) );
04893       insertText( cursor, s );
04894     }
04895   }
04896 
04897   editEnd();
04898 
04899   v->setCursorPosition( c );
04900 }
04901 
04902 void KateDocument::joinLines( uint first, uint last )
04903 {
04904 //   if ( first == last ) last += 1;
04905   editStart();
04906   int line( first );
04907   while ( first < last )
04908   {
04909     // Normalize the whitespace in the joined lines by making sure there's
04910     // always exactly one space between the joined lines
04911     // This cannot be done in editUnwrapLine, because we do NOT want this
04912     // behavior when deleting from the start of a line, just when explicitly
04913     // calling the join command
04914     KateTextLine::Ptr l = m_buffer->line( line );
04915     KateTextLine::Ptr tl = m_buffer->line( line + 1 );
04916 
04917     if ( !l || !tl )
04918     {
04919       editEnd();
04920       return;
04921     }
04922 
04923     int pos = tl->firstChar();
04924     if ( pos >= 0 )
04925     {
04926       if (pos != 0)
04927         editRemoveText( line + 1, 0, pos );
04928       if ( !( l->length() == 0 || l->at( l->length() - 1 ).isSpace() ) )
04929         editInsertText( line + 1, 0, " " );
04930     }
04931     else
04932     {
04933       // Just remove the whitespace and let Kate handle the rest
04934       editRemoveText( line + 1, 0, tl->length() );
04935     }
04936 
04937     editUnWrapLine( line );
04938     first++;
04939   }
04940   editEnd();
04941 }
04942 
04943 QString KateDocument::getWord( const KTextEditor::Cursor& cursor )
04944 {
04945   int start, end, len;
04946 
04947   KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line());
04948   len = textLine->length();
04949   start = end = cursor.column();
04950   if (start > len)        // Probably because of non-wrapping cursor mode.
04951     return QString("");
04952 
04953   while (start > 0 && highlight()->isInWord(textLine->at(start - 1), textLine->attribute(start - 1))) start--;
04954   while (end < len && highlight()->isInWord(textLine->at(end), textLine->attribute(end))) end++;
04955   len = end - start;
04956   return textLine->string().mid(start, len);
04957 }
04958 
04959 void KateDocument::tagLines(int start, int end)
04960 {
04961   foreach(KateView *view,m_views)
04962     view->tagLines (start, end, true);
04963 }
04964 
04965 void KateDocument::tagLines(KTextEditor::Cursor start, KTextEditor::Cursor end)
04966 {
04967   // May need to switch start/end cols if in block selection mode
04968 /*  if (blockSelectionMode() && start.column() > end.column()) {
04969     int sc = start.column();
04970     start.setColumn(end.column());
04971     end.setColumn(sc);
04972   }
04973 */
04974   foreach (KateView* view, m_views)
04975     view->tagLines(start, end, true);
04976 }
04977 
04978 void KateDocument::repaintViews(bool paintOnlyDirty)
04979 {
04980   foreach(KateView *view,m_views)
04981     view->repaintText(paintOnlyDirty);
04982 }
04983 
04984 void KateDocument::tagAll()
04985 {
04986   foreach(KateView *view,m_views)
04987   {
04988     view->tagAll();
04989     view->updateView (true);
04990   }
04991 }
04992 
04993 inline bool isStartBracket( const QChar& c ) { return c == '{' || c == '[' || c == '('; }
04994 inline bool isEndBracket  ( const QChar& c ) { return c == '}' || c == ']' || c == ')'; }
04995 inline bool isBracket     ( const QChar& c ) { return isStartBracket( c ) || isEndBracket( c ); }
04996 
04997 /*
04998    Bracket matching uses the following algorithm:
04999    If in overwrite mode, match the bracket currently underneath the cursor.
05000    Otherwise, if the character to the right of the cursor is an starting bracket,
05001    match it. Otherwise if the character to the left of the cursor is a
05002    ending bracket, match it. Otherwise, if the the character to the left
05003    of the cursor is an starting bracket, match it. Otherwise, if the character
05004    to the right of the cursor is an ending bracket, match it. Otherwise, don't
05005    match anything.
05006 */
05007 void KateDocument::newBracketMark( const KTextEditor::Cursor& cursor, KTextEditor::Range& bm, int maxLines )
05008 {
05009   bm.start() = cursor;
05010 
05011   if( findMatchingBracket( bm, maxLines ) )
05012     return;
05013 
05014   bm = KTextEditor::Range::invalid();
05015 
05016  // const int tw = config()->tabWidth();
05017  // const int indentStart = m_buffer->plainLine(bm.start().line())->indentDepth(tw);
05018  // const int indentEnd = m_buffer->plainLine(bm.end().line())->indentDepth(tw);
05019   //bm.setIndentMin(qMin(indentStart, indentEnd));
05020 }
05021 
05022 bool KateDocument::findMatchingBracket( KTextEditor::Range& range, int maxLines )
05023 {
05024   KateTextLine::Ptr textLine = m_buffer->plainLine( range.start().line() );
05025   if( !textLine )
05026     return false;
05027 
05028   QChar right = textLine->at( range.start().column() );
05029   QChar left  = textLine->at( range.start().column() - 1 );
05030   QChar bracket;
05031 
05032   if ( config()->configFlags() & KateDocumentConfig::cfOvr ) {
05033     if( isBracket( right ) ) {
05034       bracket = right;
05035     } else {
05036       return false;
05037     }
05038   } else if ( isStartBracket( right ) ) {
05039     bracket = right;
05040   } else if ( isEndBracket( left ) ) {
05041     range.start().setColumn(range.start().column() - 1);
05042     bracket = left;
05043   } else if ( isBracket( left ) ) {
05044     range.start().setColumn(range.start().column() - 1);
05045     bracket = left;
05046   } else if ( isBracket( right ) ) {
05047     bracket = right;
05048   } else {
05049     return false;
05050   }
05051 
05052   QChar opposite;
05053 
05054   switch( bracket.toAscii() ) {
05055   case '{': opposite = '}'; break;
05056   case '}': opposite = '{'; break;
05057   case '[': opposite = ']'; break;
05058   case ']': opposite = '['; break;
05059   case '(': opposite = ')'; break;
05060   case ')': opposite = '('; break;
05061   default: return false;
05062   }
05063 
05064   bool forward = isStartBracket( bracket );
05065   uint nesting = 0;
05066 
05067   int minLine = qMax( range.start().line() - maxLines, 0 );
05068   int maxLine = qMin( range.start().line() + maxLines, documentEnd().line() );
05069 
05070   range.end() = range.start();
05071   KateDocCursor cursor(range.start(), this);
05072   uchar validAttr = cursor.currentAttrib();
05073 
05074   while( cursor.line() >= minLine && cursor.line() <= maxLine ) {
05075 
05076     if( forward )
05077       cursor.moveForward(1);
05078     else
05079       cursor.moveBackward(1);
05080 
05081     if( !cursor.validPosition() )
05082       return false;
05083 
05084     if( cursor.currentAttrib() == validAttr )
05085     {
05086       /* Check for match */
05087       QChar c = cursor.currentChar();
05088       if( c == bracket ) {
05089         nesting++;
05090       } else if( c == opposite ) {
05091         if( nesting == 0 ) {
05092           if( forward )
05093             range.end() = cursor;
05094           else
05095             range.start() = cursor;
05096           return true;
05097         }
05098         nesting--;
05099       }
05100     }
05101 
05102     if(cursor == KTextEditor::Cursor(0,0) || cursor >= documentEnd())
05103       return false;
05104   }
05105 
05106   return false;
05107 }
05108 
05109 void KateDocument::guiActivateEvent( KParts::GUIActivateEvent *ev )
05110 {
05111   KParts::ReadWritePart::guiActivateEvent( ev );
05112   //if ( ev->activated() )
05113   //  emit selectionChanged();
05114 }
05115 
05116 void KateDocument::setDocName (QString name )
05117 {
05118   if ( name == m_docName )
05119     return;
05120 
05121   if ( !name.isEmpty() )
05122   {
05123     // TODO check for similarly named documents
05124     m_docName = name;
05125     emit documentNameChanged (this);
05126     return;
05127   }
05128 
05129   // if the name is set, and starts with FILENAME, it should not be changed!
05130   if ( ! url().isEmpty() && m_docName.startsWith( url().fileName() ) ) return;
05131 
05132   int count = -1;
05133 
05134   for (int z=0; z < KateGlobal::self()->kateDocuments().size(); ++z)
05135   {
05136     KateDocument *doc = (KateGlobal::self()->kateDocuments())[z];
05137 
05138     if ( (doc != this) && (doc->url().fileName() == url().fileName()) )
05139       if ( doc->m_docNameNumber > count )
05140         count = doc->m_docNameNumber;
05141   }
05142 
05143   m_docNameNumber = count + 1;
05144 
05145   m_docName = url().fileName();
05146 
05147   if (m_docName.isEmpty())
05148     m_docName = i18n ("Untitled");
05149 
05150   if (m_docNameNumber > 0)
05151     m_docName = QString(m_docName + " (%1)").arg(m_docNameNumber+1);
05152 
05153   emit documentNameChanged (this);
05154 }
05155 
05156 void KateDocument::slotModifiedOnDisk( KTextEditor::View * /*v*/ )
05157 {
05158   if ( m_isasking < 0 )
05159   {
05160     m_isasking = 0;
05161     return;
05162   }
05163 
05164   if ( !s_fileChangedDialogsActivated || m_isasking )
05165     return;
05166 
05167   if (m_modOnHd && !url().isEmpty())
05168   {
05169     m_isasking = 1;
05170 
05171     QWidget *parentWidget(dialogParent());
05172 
05173     KateModOnHdPrompt p( this, m_modOnHdReason, reasonedMOHString(), parentWidget );
05174     switch ( p.exec() )
05175     {
05176       case KateModOnHdPrompt::Save:
05177       {
05178         m_modOnHd = false;
05179         KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
05180             url().url(),QString(),parentWidget,i18n("Save File"));
05181 
05182         kDebug(13020)<<"got "<<res.URLs.count()<<" URLs";
05183         if( ! res.URLs.isEmpty() && ! res.URLs.first().isEmpty() && checkOverwrite( res.URLs.first(), parentWidget ) )
05184         {
05185           setEncoding( res.encoding );
05186 
05187           if( ! saveAs( res.URLs.first() ) )
05188           {
05189             KMessageBox::error( parentWidget, i18n("Save failed") );
05190             m_modOnHd = true;
05191           }
05192           else
05193             emit modifiedOnDisk( this, false, OnDiskUnmodified );
05194         }
05195         else // the save as dialog was canceled, we are still modified on disk
05196         {
05197           m_modOnHd = true;
05198         }
05199 
05200         m_isasking = 0;
05201         break;
05202       }
05203 
05204       case KateModOnHdPrompt::Reload:
05205         m_modOnHd = false;
05206         emit modifiedOnDisk( this, false, OnDiskUnmodified );
05207         documentReload();
05208         m_isasking = 0;
05209         break;
05210 
05211       case KateModOnHdPrompt::Ignore:
05212         m_modOnHd = false;
05213         emit modifiedOnDisk( this, false, OnDiskUnmodified );
05214         m_isasking = 0;
05215         break;
05216 
05217       case KateModOnHdPrompt::Overwrite:
05218         m_modOnHd = false;
05219         emit modifiedOnDisk( this, false, OnDiskUnmodified );
05220         m_isasking = 0;
05221         save();
05222         break;
05223 
05224       default: // Delay/cancel: ignore next focus event
05225         m_isasking = -1;
05226     }
05227   }
05228 }
05229 
05230 void KateDocument::setModifiedOnDisk( ModifiedOnDiskReason reason )
05231 {
05232   m_modOnHdReason = reason;
05233   m_modOnHd = (reason != OnDiskUnmodified);
05234   emit modifiedOnDisk( this, (reason != OnDiskUnmodified), reason );
05235 }
05236 
05237 class KateDocumentTmpMark
05238 {
05239   public:
05240     QString line;
05241     KTextEditor::Mark mark;
05242 };
05243 
05244 void KateDocument::setModifiedOnDiskWarning (bool on)
05245 {
05246   s_fileChangedDialogsActivated = on;
05247 }
05248 
05249 bool KateDocument::documentReload()
05250 {
05251   if ( !url().isEmpty() )
05252   {
05253     if (m_modOnHd && s_fileChangedDialogsActivated)
05254     {
05255       QWidget *parentWidget(dialogParent());
05256 
05257       int i = KMessageBox::warningYesNoCancel
05258                 (parentWidget, reasonedMOHString() + "\n\n" + i18n("What do you want to do?"),
05259                 i18n("File Was Changed on Disk"), KGuiItem(i18n("&Reload File")), KGuiItem(i18n("&Ignore Changes")));
05260 
05261       if ( i != KMessageBox::Yes)
05262       {
05263         if (i == KMessageBox::No)
05264         {
05265           m_modOnHd = false;
05266           m_modOnHdReason = OnDiskUnmodified;
05267           emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
05268         }
05269 
05270         return false;
05271       }
05272     }
05273 
05274     emit aboutToReload(this);
05275 
05276     if (clearOnDocumentReload())
05277       m_smartManager->clear(false);
05278 
05279     QList<KateDocumentTmpMark> tmp;
05280 
05281     for (QHash<int, KTextEditor::Mark*>::const_iterator i = m_marks.constBegin(); i != m_marks.constEnd(); ++i)
05282     {
05283       KateDocumentTmpMark m;
05284 
05285       m.line = line (i.value()->line);
05286       m.mark = *i.value();
05287 
05288       tmp.append (m);
05289     }
05290 
05291     QString oldMode = mode ();
05292     bool byUser = m_fileTypeSetByUser;
05293 
05294     m_storedVariables.clear();
05295 
05296     // save cursor positions for all views
05297     QVector<KTextEditor::Cursor> cursorPositions;
05298     cursorPositions.reserve(m_views.size());
05299     foreach (KateView *v, m_views)
05300       cursorPositions.append( v->cursorPosition() );
05301 
05302     m_reloading = true;
05303     KateDocument::openUrl( url() );
05304     m_reloading = false;
05305 
05306     // restore cursor positions for all views
05307     QLinkedList<KateView*>::iterator it = m_views.begin();
05308     for(int i = 0; i < m_views.size(); ++i, ++it)
05309       (*it)->setCursorPositionInternal( cursorPositions[i], m_config->tabWidth(), false );
05310 
05311     for (int z=0; z < tmp.size(); z++)
05312     {
05313       if (z < (int)lines())
05314       {
05315         if (line(tmp[z].mark.line) == tmp[z].line)
05316           setMark (tmp[z].mark.line, tmp[z].mark.type);
05317       }
05318     }
05319 
05320     if (byUser)
05321       setMode (oldMode);
05322 
05323     return true;
05324   }
05325 
05326   return false;
05327 }
05328 
05329 bool KateDocument::documentSave()
05330 {
05331   if( !url().isValid() || !isReadWrite() )
05332     return documentSaveAs();
05333 
05334   return save();
05335 }
05336 
05337 bool KateDocument::documentSaveAs()
05338 {
05339   QWidget *parentWidget(dialogParent());
05340 
05341   KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
05342                 url().url(),QString(),parentWidget,i18n("Save File"));
05343 
05344   if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first(), parentWidget ) )
05345     return false;
05346 
05347   setEncoding( res.encoding );
05348 
05349   return saveAs( res.URLs.first() );
05350 }
05351 
05352 void KateDocument::setWordWrap (bool on)
05353 {
05354   config()->setWordWrap (on);
05355 }
05356 
05357 bool KateDocument::wordWrap () const
05358 {
05359   return config()->wordWrap ();
05360 }
05361 
05362 void KateDocument::setWordWrapAt (uint col)
05363 {
05364   config()->setWordWrapAt (col);
05365 }
05366 
05367 unsigned int KateDocument::wordWrapAt () const
05368 {
05369   return config()->wordWrapAt ();
05370 }
05371 
05372 void KateDocument::setPageUpDownMovesCursor (bool on)
05373 {
05374   config()->setPageUpDownMovesCursor (on);
05375 }
05376 
05377 bool KateDocument::pageUpDownMovesCursor () const
05378 {
05379   return config()->pageUpDownMovesCursor ();
05380 }
05381 
05382 void KateDocument::dumpRegionTree()
05383 {
05384   m_buffer->foldingTree()->debugDump();
05385 }
05386 //END
05387 
05388 void KateDocument::lineInfo (KateLineInfo *info, unsigned int line)
05389 {
05390   m_buffer->lineInfo(info,line);
05391 }
05392 
05393 KateCodeFoldingTree *KateDocument::foldingTree ()
05394 {
05395   return m_buffer->foldingTree();
05396 }
05397 
05398 bool KateDocument::setEncoding (const QString &e)
05399 {
05400   return m_config->setEncoding(e);
05401 }
05402 
05403 const QString &KateDocument::encoding() const
05404 {
05405   return m_config->encoding();
05406 }
05407 
05408 void KateDocument::setScriptForEncodingAutoDetection (KEncodingDetector::AutoDetectScript script)
05409 {
05410   m_config->setEncodingAutoDetectionScript(script);
05411 }
05412 
05413 KEncodingDetector::AutoDetectScript KateDocument::scriptForEncodingAutoDetection() const
05414 {
05415   return m_config->encodingAutoDetectionScript();
05416 }
05417 
05418 void KateDocument::updateConfig ()
05419 {
05420   emit undoChanged ();
05421   tagAll();
05422 
05423   foreach (KateView * view,m_views)
05424   {
05425     view->updateDocumentConfig ();
05426   }
05427 
05428   // switch indenter if needed and update config....
05429   m_indenter.setMode (m_config->indentationMode());
05430   m_indenter.updateConfig();
05431 
05432   m_buffer->setTabWidth (config()->tabWidth());
05433 }
05434 
05435 //BEGIN Variable reader
05436 // "local variable" feature by anders, 2003
05437 /* TODO
05438       add config options (how many lines to read, on/off)
05439       add interface for plugins/apps to set/get variables
05440       add view stuff
05441 */
05442 QRegExp KateDocument::kvLine = QRegExp("kate:(.*)");
05443 QRegExp KateDocument::kvLineWildcard = QRegExp("kate-wildcard\\((.*)\\):(.*)");
05444 QRegExp KateDocument::kvLineMime = QRegExp("kate-mimetype\\((.*)\\):(.*)");
05445 QRegExp KateDocument::kvVar = QRegExp("([\\w\\-]+)\\s+([^;]+)");
05446 
05447 void KateDocument::readVariables(bool onlyViewAndRenderer)
05448 {
05449   if (!onlyViewAndRenderer)
05450     m_config->configStart();
05451 
05452   // views!
05453   KateView *v;
05454   foreach (v,m_views)
05455   {
05456     v->config()->configStart();
05457     v->renderer()->config()->configStart();
05458   }
05459   // read a number of lines in the top/bottom of the document
05460   for (int i=0; i < qMin( 9, lines() ); ++i )
05461   {
05462     readVariableLine( line( i ), onlyViewAndRenderer );
05463   }
05464   if ( lines() > 10 )
05465   {
05466     for ( int i = qMax( 10, lines() - 10); i < lines(); i++ )
05467     {
05468       readVariableLine( line( i ), onlyViewAndRenderer );
05469     }
05470   }
05471 
05472   if (!onlyViewAndRenderer)
05473     m_config->configEnd();
05474 
05475   foreach (v,m_views)
05476   {
05477     v->config()->configEnd();
05478     v->renderer()->config()->configEnd();
05479   }
05480 }
05481 
05482 void KateDocument::readVariableLine( QString t, bool onlyViewAndRenderer )
05483 {
05484   // simple check first, no regex
05485   // no kate inside, no vars, simple...
05486   if (!t.contains("kate"))
05487     return;
05488 
05489   // found vars, if any
05490   QString s;
05491 
05492   // now, try first the normal ones
05493   if ( kvLine.indexIn( t ) > -1 )
05494   {
05495     s = kvLine.cap(1);
05496 
05497     kDebug (13020) << "normal variable line kate: matched: " << s;
05498   }
05499   else if (kvLineWildcard.indexIn( t ) > -1) // regex given
05500   {
05501     QStringList wildcards (kvLineWildcard.cap(1).split (';', QString::SkipEmptyParts));
05502     QString nameOfFile = url().fileName();
05503 
05504     bool found = false;
05505     for (int i = 0; !found && i < wildcards.size(); ++i)
05506     {
05507       QRegExp wildcard (wildcards[i], Qt::CaseSensitive, QRegExp::Wildcard);
05508 
05509       found = wildcard.exactMatch (nameOfFile);
05510     }
05511 
05512     // nothing usable found...
05513     if (!found)
05514       return;
05515 
05516     s = kvLineWildcard.cap(2);
05517 
05518     kDebug (13020) << "guarded variable line kate-wildcard: matched: " << s;
05519   }
05520   else if (kvLineMime.indexIn( t ) > -1) // mime-type given
05521   {
05522     QStringList types (kvLineMime.cap(1).split (';', QString::SkipEmptyParts));
05523 
05524     // no matching type found
05525     if (!types.contains (mimeType ()))
05526       return;
05527 
05528     s = kvLineMime.cap(2);
05529 
05530     kDebug (13020) << "guarded variable line kate-mimetype: matched: " << s;
05531   }
05532   else // nothing found
05533   {
05534     return;
05535   }
05536 
05537   QStringList vvl; // view variable names
05538   vvl << "dynamic-word-wrap" << "dynamic-word-wrap-indicators"
05539       << "line-numbers" << "icon-border" << "folding-markers"
05540       << "bookmark-sorting" << "auto-center-lines"
05541       << "icon-bar-color"
05542       // renderer
05543       << "background-color" << "selection-color"
05544       << "current-line-color" << "bracket-highlight-color"
05545       << "word-wrap-marker-color"
05546       << "font" << "font-size" << "scheme";
05547   int spaceIndent = -1;  // for backward compatibility; see below
05548   bool replaceTabsSet = false;
05549   int p( 0 );
05550 
05551   QString  var, val;
05552   while ( (p = kvVar.indexIn( s, p )) > -1 )
05553   {
05554     p += kvVar.matchedLength();
05555     var = kvVar.cap( 1 );
05556     val = kvVar.cap( 2 ).trimmed();
05557     bool state; // store booleans here
05558     int n; // store ints here
05559 
05560     // only apply view & renderer config stuff
05561     if (onlyViewAndRenderer)
05562     {
05563       if ( vvl.contains( var ) ) // FIXME define above
05564         setViewVariable( var, val );
05565     }
05566     else
05567     {
05568       // BOOL  SETTINGS
05569       if ( var == "word-wrap" && checkBoolValue( val, &state ) )
05570         setWordWrap( state ); // ??? FIXME CHECK
05571       // KateConfig::configFlags
05572       // FIXME should this be optimized to only a few calls? how?
05573       else if ( var == "backspace-indents" && checkBoolValue( val, &state ) )
05574         m_config->setConfigFlags( KateDocumentConfig::cfBackspaceIndents, state );
05575       else if ( var == "replace-tabs" && checkBoolValue( val, &state ) )
05576       {
05577         m_config->setConfigFlags( KateDocumentConfig::cfReplaceTabsDyn, state );
05578         replaceTabsSet = true;  // for backward compatibility; see below
05579       }
05580       else if ( var == "remove-trailing-space" && checkBoolValue( val, &state ) )
05581         m_config->setConfigFlags( KateDocumentConfig::cfRemoveTrailingDyn, state );
05582       else if ( var == "wrap-cursor" && checkBoolValue( val, &state ) )
05583         m_config->setConfigFlags( KateDocumentConfig::cfWrapCursor, state );
05584       else if ( var == "auto-brackets" && checkBoolValue( val, &state ) )
05585         m_config->setConfigFlags( KateDocumentConfig::cfAutoBrackets, state );
05586       else if ( var == "overwrite-mode" && checkBoolValue( val, &state ) )
05587         m_config->setConfigFlags( KateDocumentConfig::cfOvr, state );
05588       else if ( var == "keep-extra-spaces" && checkBoolValue( val, &state ) )
05589         m_config->setConfigFlags( KateDocumentConfig::cfKeepExtraSpaces, state );
05590       else if ( var == "tab-indents" && checkBoolValue( val, &state ) )
05591         m_config->setConfigFlags( KateDocumentConfig::cfTabIndents, state );
05592       else if ( var == "show-tabs" && checkBoolValue( val, &state ) )
05593         m_config->setConfigFlags( KateDocumentConfig::cfShowTabs, state );
05594       else if ( var == "show-trailing-spaces" && checkBoolValue( val, &state ) )
05595         m_config->setConfigFlags( KateDocumentConfig::cfShowSpaces, state );
05596       else if ( var == "space-indent" && checkBoolValue( val, &state ) )
05597       {
05598         // this is for backward compatibility; see below
05599         spaceIndent = state;
05600       }
05601       else if ( var == "smart-home" && checkBoolValue( val, &state ) )
05602         m_config->setConfigFlags( KateDocumentConfig::cfSmartHome, state );
05603       else if ( var == "replace-trailing-space-save" && checkBoolValue( val, &state ) )
05604         m_config->setConfigFlags( KateDocumentConfig::cfRemoveSpaces, state );
05605 
05606       // INTEGER SETTINGS
05607       else if ( var == "tab-width" && checkIntValue( val, &n ) )
05608         m_config->setTabWidth( n );
05609       else if ( var == "indent-width"  && checkIntValue( val, &n ) )
05610         m_config->setIndentationWidth( n );
05611       else if ( var == "indent-mode" )
05612       {
05613         m_config->setIndentationMode( val );
05614       }
05615       else if ( var == "word-wrap-column" && checkIntValue( val, &n ) && n > 0 ) // uint, but hard word wrap at 0 will be no fun ;)
05616         m_config->setWordWrapAt( n );
05617 
05618       // STRING SETTINGS
05619       else if ( var == "eol" || var == "end-of-line" )
05620       {
05621         QStringList l;
05622         l << "unix" << "dos" << "mac";
05623         if ( (n = l.indexOf( val.toLower() )) != -1 )
05624           m_config->setEol( n );
05625       }
05626       else if ( var == "encoding" )
05627         m_config->setEncoding( val );
05628       else if (var == "presave-postdialog")
05629         setPreSavePostDialogFilterChecks(val.split(','));
05630       else if (var == "postload")
05631         setPostLoadFilterChecks(val.split(','));
05632       else if ( var == "syntax" || var == "hl" )
05633       {
05634         setHighlightingMode( val );
05635       }
05636       else if ( var == "mode" )
05637       {
05638         setMode( val );
05639       }
05640 
05641       // VIEW SETTINGS
05642       else if ( vvl.contains( var ) )
05643         setViewVariable( var, val );
05644       else
05645       {
05646         m_storedVariables.insert( var, val );
05647         emit variableChanged( this, var, val );
05648       }
05649     }
05650   }
05651 
05652   // Backward compatibility
05653   // If space-indent was set, but replace-tabs was not set, we assume
05654   // that the user wants to replace tabulators and set that flag.
05655   // If both were set, replace-tabs has precedence.
05656   // At this point spaceIndent is -1 if it was never set,
05657   // 0 if it was set to off, and 1 if it was set to on.
05658   // Note that if onlyViewAndRenderer was requested, spaceIndent is -1.
05659   if ( !replaceTabsSet && spaceIndent >= 0 )
05660   {
05661     m_config->setConfigFlags( KateDocumentConfig::cfReplaceTabsDyn, spaceIndent > 0 );
05662   }
05663 }
05664 
05665 void KateDocument::setViewVariable( QString var, QString val )
05666 {
05667   KateView *v;
05668   bool state;
05669   int n;
05670   QColor c;
05671   foreach (v,m_views)
05672   {
05673     if ( var == "dynamic-word-wrap" && checkBoolValue( val, &state ) )
05674       v->config()->setDynWordWrap( state );
05675     else if ( var == "persistent-selection" && checkBoolValue( val, &state ) )
05676       v->config()->setPersistentSelection( state );
05677     else if ( var == "block-selection"  && checkBoolValue( val, &state ) )
05678           v->setBlockSelectionMode( state );
05679     //else if ( var = "dynamic-word-wrap-indicators" )
05680     else if ( var == "line-numbers" && checkBoolValue( val, &state ) )
05681       v->config()->setLineNumbers( state );
05682     else if (var == "icon-border" && checkBoolValue( val, &state ) )
05683       v->config()->setIconBar( state );
05684     else if (var == "folding-markers" && checkBoolValue( val, &state ) )
05685       v->config()->setFoldingBar( state );
05686     else if ( var == "auto-center-lines" && checkIntValue( val, &n ) )
05687       v->config()->setAutoCenterLines( n ); // FIXME uint, > N ??
05688     else if ( var == "icon-bar-color" && checkColorValue( val, c ) )
05689       v->renderer()->config()->setIconBarColor( c );
05690     // RENDERER
05691     else if ( var == "background-color" && checkColorValue( val, c ) )
05692       v->renderer()->config()->setBackgroundColor( c );
05693     else if ( var == "selection-color" && checkColorValue( val, c ) )
05694       v->renderer()->config()->setSelectionColor( c );
05695     else if ( var == "current-line-color" && checkColorValue( val, c ) )
05696       v->renderer()->config()->setHighlightedLineColor( c );
05697     else if ( var == "bracket-highlight-color" && checkColorValue( val, c ) )
05698       v->renderer()->config()->setHighlightedBracketColor( c );
05699     else if ( var == "word-wrap-marker-color" && checkColorValue( val, c ) )
05700       v->renderer()->config()->setWordWrapMarkerColor( c );
05701     else if ( var == "font" || ( var == "font-size" && checkIntValue( val, &n ) ) )
05702     {
05703       QFont _f( v->renderer()->config()->font() );
05704 
05705       if ( var == "font" )
05706       {
05707         _f.setFamily( val );
05708         _f.setFixedPitch( QFont( val ).fixedPitch() );
05709       }
05710       else
05711         _f.setPointSize( n );
05712 
05713       v->renderer()->config()->setFont( _f );
05714     }
05715     else if ( var == "scheme" )
05716     {
05717       v->renderer()->config()->setSchema( val );
05718     }
05719   }
05720 }
05721 
05722 bool KateDocument::checkBoolValue( QString val, bool *result )
05723 {
05724   val = val.trimmed().toLower();
05725   QStringList l;
05726   l << "1" << "on" << "true";
05727   if ( l.contains( val ) )
05728   {
05729     *result = true;
05730     return true;
05731   }
05732   l.clear();
05733   l << "0" << "off" << "false";
05734   if ( l.contains( val ) )
05735   {
05736     *result = false;
05737     return true;
05738   }
05739   return false;
05740 }
05741 
05742 bool KateDocument::checkIntValue( QString val, int *result )
05743 {
05744   bool ret( false );
05745   *result = val.toInt( &ret );
05746   return ret;
05747 }
05748 
05749 bool KateDocument::checkColorValue( QString val, QColor &c )
05750 {
05751   c.setNamedColor( val );
05752   return c.isValid();
05753 }
05754 
05755 // KTextEditor::variable
05756 QString KateDocument::variable( const QString &name ) const
05757 {
05758   if ( m_storedVariables.contains( name ) )
05759     return m_storedVariables[ name ];
05760 
05761   return "";
05762 }
05763 
05764 //END
05765 
05766 void KateDocument::slotModOnHdDirty (const QString &path)
05767 {
05768   if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskModified))
05769   {
05770     // compare md5 with the one we have (if we have one)
05771     if ( ! m_digest.isEmpty() )
05772     {
05773       QByteArray tmp;
05774       if ( createDigest( tmp ) && tmp == m_digest )
05775         return;
05776     }
05777 
05778     m_modOnHd = true;
05779     m_modOnHdReason = OnDiskModified;
05780 
05781     // reenable dialog if not running atm
05782     if (m_isasking == -1)
05783       m_isasking = false;
05784 
05785     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
05786   }
05787 }
05788 
05789 void KateDocument::slotModOnHdCreated (const QString &path)
05790 {
05791   if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskCreated))
05792   {
05793     m_modOnHd = true;
05794     m_modOnHdReason = OnDiskCreated;
05795 
05796     // reenable dialog if not running atm
05797     if (m_isasking == -1)
05798       m_isasking = false;
05799 
05800     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
05801   }
05802 }
05803 
05804 void KateDocument::slotModOnHdDeleted (const QString &path)
05805 {
05806   if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != OnDiskDeleted))
05807   {
05808     m_modOnHd = true;
05809     m_modOnHdReason = OnDiskDeleted;
05810 
05811     // reenable dialog if not running atm
05812     if (m_isasking == -1)
05813       m_isasking = false;
05814 
05815     emit modifiedOnDisk (this, m_modOnHd, m_modOnHdReason);
05816   }
05817 }
05818 
05819 bool KateDocument::createDigest( QByteArray &result )
05820 {
05821   bool ret = false;
05822   result = "";
05823   if ( url().isLocalFile() )
05824   {
05825     QFile f ( url().path() );
05826     if ( f.open( QIODevice::ReadOnly) )
05827     {
05828       KMD5 md5;
05829       ret = md5.update( f );
05830       md5.hexDigest( result );
05831       f.close();
05832       ret = true;
05833     }
05834   }
05835   return ret;
05836 }
05837 
05838 QString KateDocument::reasonedMOHString() const
05839 {
05840   switch( m_modOnHdReason )
05841   {
05842     case OnDiskModified:
05843       return i18n("The file '%1' was modified by another program.",  url().pathOrUrl() );
05844       break;
05845     case OnDiskCreated:
05846       return i18n("The file '%1' was created by another program.",  url().pathOrUrl() );
05847       break;
05848     case OnDiskDeleted:
05849       return i18n("The file '%1' was deleted by another program.",  url().pathOrUrl() );
05850       break;
05851     default:
05852       return QString();
05853   }
05854 }
05855 
05856 void KateDocument::removeTrailingSpace(int line)
05857 {
05858   // remove trailing spaces from left line if required
05859   if (m_blockRemoveTrailingSpaces
05860       || !(config()->configFlags() & KateDocumentConfig::cfRemoveTrailingDyn))
05861     return;
05862 
05863   KateTextLine::Ptr ln = plainKateTextLine(line);
05864 
05865   if (!ln || ln->length() == 0)
05866     return;
05867 
05868   if (line == activeView()->cursorPosition().line()
05869       && activeView()->cursorPosition().column() >= qMax(0, ln->lastChar()))
05870     return;
05871 
05872   const int p = ln->lastChar() + 1;
05873   const int l = ln->length() - p;
05874   if (l > 0) {
05875     m_blockRemoveTrailingSpaces = true;
05876     editRemoveText(line, p, l);
05877     m_blockRemoveTrailingSpaces = false;
05878   }
05879 }
05880 
05881 void KateDocument::updateFileType (const QString &newType, bool user)
05882 {
05883   if (user || !m_fileTypeSetByUser)
05884   {
05885     if (!newType.isEmpty())
05886     {
05887           m_fileType = newType;
05888 
05889           m_config->configStart();
05890 
05891           if (!hlSetByUser && !KateGlobal::self()->modeManager()->fileType(newType).hl.isEmpty())
05892           {
05893             int hl (KateHlManager::self()->nameFind (KateGlobal::self()->modeManager()->fileType(newType).hl));
05894 
05895             if (hl >= 0)
05896               m_buffer->setHighlight(hl);
05897           }
05898 
05899 
05900           // views!
05901           KateView *v;
05902           foreach (v,m_views)
05903           {
05904             v->config()->configStart();
05905             v->renderer()->config()->configStart();
05906           }
05907 
05908           readVariableLine( KateGlobal::self()->modeManager()->fileType(newType).varLine );
05909 
05910           m_config->configEnd();
05911           foreach (v,m_views)
05912           {
05913             v->config()->configEnd();
05914             v->renderer()->config()->configEnd();
05915           }
05916     }
05917   }
05918 
05919   // fixme, make this better...
05920   emit modeChanged (this);
05921 }
05922 
05923 void KateDocument::slotQueryClose_save(bool *handled, bool* abortClosing) {
05924       *handled=true;
05925       *abortClosing=true;
05926       if (this->url().isEmpty())
05927       {
05928         QWidget *parentWidget(dialogParent());
05929 
05930         KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveUrlAndEncoding(config()->encoding(),
05931                 QString(),QString(),parentWidget,i18n("Save File"));
05932 
05933         if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first(), parentWidget ) ) {
05934                 *abortClosing=true;
05935                 return;
05936         }
05937         setEncoding( res.encoding );
05938           saveAs( res.URLs.first() );
05939         *abortClosing=false;
05940       }
05941       else
05942       {
05943           save();
05944           *abortClosing=false;
05945       }
05946 
05947 }
05948 
05949 bool KateDocument::checkOverwrite( KUrl u, QWidget *parent )
05950 {
05951   if( !u.isLocalFile() )
05952     return true;
05953 
05954   QFileInfo info( u.path() );
05955   if( !info.exists() )
05956     return true;
05957 
05958   return KMessageBox::Cancel != KMessageBox::warningContinueCancel( parent,
05959     i18n( "A file named \"%1\" already exists. "
05960           "Are you sure you want to overwrite it?" ,  info.fileName() ),
05961     i18n( "Overwrite File?" ),
05962     KGuiItem(i18n( "&Overwrite" )) );
05963 }
05964 
05965 
05966 //BEGIN KTextEditor::TemplateInterface
05967 bool KateDocument::insertTemplateTextImplementation ( const KTextEditor::Cursor &c, const QString &templateString, const QMap<QString,QString> &initialValues, QWidget *) {
05968       return (new KateTemplateHandler(this,c,templateString,initialValues))->initOk();
05969 }
05970 
05971 void KateDocument::testTemplateCode() {
05972   //qobject_cast<KTextEditor::TemplateInterface*>(activeView())->insertTemplateText(activeView()->cursorPosition(),"for ${index} \\${NOPLACEHOLDER} ${index} ${blah} ${fullname} \\$${Placeholder} \\${${PLACEHOLDER2}}\n next line:${ANOTHERPLACEHOLDER} $${DOLLARBEFOREPLACEHOLDER} {NOTHING} {\n${cursor}\n}",QMap<QString,QString>());
05973   qobject_cast<KTextEditor::TemplateInterface*>(activeView())->insertTemplateText(activeView()->cursorPosition(),"for ${index} \\${NOPLACEHOLDER} ${index} ${blah} \\$${Placeholder} \\${${PLACEHOLDER2}}\n next line:${ANOTHERPLACEHOLDER} $${DOLLARBEFOREPLACEHOLDER} {NOTHING} {\n${cursor}\n}",QMap<QString,QString>());
05974 }
05975 
05976 
05977 bool KateDocument::invokeTabInterceptor(int key) {
05978   if (m_tabInterceptor) return (*m_tabInterceptor)(key);
05979   return false;
05980 }
05981 
05982 bool KateDocument::setTabInterceptor(KateKeyInterceptorFunctor *interceptor) {
05983   if (m_tabInterceptor) return false;
05984   m_tabInterceptor=interceptor;
05985   return true;
05986 }
05987 
05988 bool KateDocument::removeTabInterceptor(KateKeyInterceptorFunctor *interceptor) {
05989   if (m_tabInterceptor!=interceptor) return false;
05990   m_tabInterceptor=0;
05991   return true;
05992 }
05993 
05994 KateView * KateDocument::activeKateView( ) const
05995 {
05996   return static_cast<KateView*>(m_activeView);
05997 }
05998 
05999 KTextEditor::Cursor KateDocument::documentEnd( ) const
06000 {
06001   return KTextEditor::Cursor(lastLine(), lineLength(lastLine()));
06002 }
06003 //END KTextEditor::TemplateInterface
06004 
06005 //BEGIN KTextEditor::SmartInterface
06006 int KateDocument::currentRevision() const
06007 {
06008   return m_smartManager->currentRevision();
06009 }
06010 
06011 void KateDocument::releaseRevision(int revision) const
06012 {
06013   m_smartManager->releaseRevision(revision);
06014 }
06015 
06016 void KateDocument::useRevision(int revision)
06017 {
06018   m_smartManager->useRevision(revision);
06019 }
06020 
06021 KTextEditor::Cursor KateDocument::translateFromRevision(const KTextEditor::Cursor& cursor, KTextEditor::SmartCursor::InsertBehavior insertBehavior) const
06022 {
06023   return m_smartManager->translateFromRevision(cursor, insertBehavior);
06024 }
06025 
06026 KTextEditor::Range KateDocument::translateFromRevision(const KTextEditor::Range& range, KTextEditor::SmartRange::InsertBehaviors insertBehavior) const
06027 {
06028   return m_smartManager->translateFromRevision(range, insertBehavior);
06029 }
06030 
06031 KTextEditor::SmartCursor* KateDocument::newSmartCursor( const KTextEditor::Cursor & position, KTextEditor::SmartCursor::InsertBehavior insertBehavior )
06032 {
06033   return m_smartManager->newSmartCursor(position, insertBehavior, false);
06034 }
06035 
06036 KTextEditor::SmartRange * KateDocument::newSmartRange( const KTextEditor::Range & range, KTextEditor::SmartRange * parent, KTextEditor::SmartRange::InsertBehaviors insertBehavior )
06037 {
06038   return m_smartManager->newSmartRange( range, parent, insertBehavior, false );
06039 }
06040 
06041 KTextEditor::SmartRange * KateDocument::newSmartRange( KTextEditor::SmartCursor * start, KTextEditor::SmartCursor * end, KTextEditor::SmartRange * parent, KTextEditor::SmartRange::InsertBehaviors insertBehavior )
06042 {
06043   KateSmartCursor* kstart = dynamic_cast<KateSmartCursor*>(start);
06044   KateSmartCursor* kend = dynamic_cast<KateSmartCursor*>(end);
06045   if (!kstart || !kend)
06046     return 0L;
06047   if (kstart->range() || kend->range())
06048     return 0L;
06049   return m_smartManager->newSmartRange(kstart, kend, parent, insertBehavior, false);
06050 }
06051 
06052 void KateDocument::unbindSmartRange( KTextEditor::SmartRange * range )
06053 {
06054   m_smartManager->unbindSmartRange(range);
06055 }
06056 
06057 bool KateDocument::replaceText( const KTextEditor::Range & range, const QString & s, bool block )
06058 {
06059   // TODO more efficient?
06060   editStart();
06061   bool changed = removeText(range, block);
06062   changed |= insertText(range.start(), s, block);
06063   editEnd();
06064   return changed;
06065 }
06066 
06067 void KateDocument::addHighlightToDocument( KTextEditor::SmartRange * topRange, bool supportDynamic )
06068 {
06069   if (m_documentHighlights.contains(topRange))
06070     return;
06071 
06072   m_documentHighlights.append(topRange);
06073 
06074   // Deal with the range being deleted externally
06075   topRange->addWatcher(this);
06076 
06077   if (supportDynamic) {
06078     m_documentDynamicHighlights.append(topRange);
06079     emit dynamicHighlightAdded(static_cast<KateSmartRange*>(topRange));
06080   }
06081 
06082   foreach (KateView * view, m_views)
06083     view->addExternalHighlight(topRange, supportDynamic);
06084 }
06085 
06086 void KateDocument::removeHighlightFromDocument( KTextEditor::SmartRange * topRange )
06087 {
06088   if (!m_documentHighlights.contains(topRange))
06089     return;
06090 
06091   foreach (KateView * view, m_views)
06092     view->removeExternalHighlight(topRange);
06093 
06094   m_documentHighlights.removeAll(topRange);
06095   topRange->removeWatcher(this);
06096 
06097   if (m_documentDynamicHighlights.contains(topRange)) {
06098     m_documentDynamicHighlights.removeAll(topRange);
06099     emit dynamicHighlightRemoved(static_cast<KateSmartRange*>(topRange));
06100   }
06101 }
06102 
06103 const QList< KTextEditor::SmartRange * > KateDocument::documentHighlights( ) const
06104 {
06105   return m_documentHighlights;
06106 }
06107 
06108 void KateDocument::addHighlightToView( KTextEditor::View * view, KTextEditor::SmartRange * topRange, bool supportDynamic )
06109 {
06110   static_cast<KateView*>(view)->addExternalHighlight(topRange, supportDynamic);
06111 }
06112 
06113 void KateDocument::removeHighlightFromView( KTextEditor::View * view, KTextEditor::SmartRange * topRange )
06114 {
06115   static_cast<KateView*>(view)->removeExternalHighlight(topRange);
06116 }
06117 
06118 const QList< KTextEditor::SmartRange * > KateDocument::viewHighlights( KTextEditor::View * view ) const
06119 {
06120   return static_cast<KateView*>(view)->externalHighlights();
06121 }
06122 
06123 void KateDocument::addActionsToDocument( KTextEditor::SmartRange * topRange )
06124 {
06125   if (m_documentActions.contains(topRange))
06126     return;
06127 
06128   m_documentActions.append(topRange);
06129 
06130   // Deal with the range being deleted externally
06131   topRange->addWatcher(this);
06132 }
06133 
06134 void KateDocument::removeActionsFromDocument( KTextEditor::SmartRange * topRange )
06135 {
06136   if (!m_documentActions.contains(topRange))
06137     return;
06138 
06139   m_documentActions.removeAll(topRange);
06140   topRange->removeWatcher(this);
06141 }
06142 
06143 const QList< KTextEditor::SmartRange * > KateDocument::documentActions( ) const
06144 {
06145   return m_documentActions;
06146 }
06147 
06148 void KateDocument::addActionsToView( KTextEditor::View * view, KTextEditor::SmartRange * topRange )
06149 {
06150   static_cast<KateView*>(view)->addActions(topRange);
06151 }
06152 
06153 void KateDocument::removeActionsFromView( KTextEditor::View * view, KTextEditor::SmartRange * topRange )
06154 {
06155   static_cast<KateView*>(view)->removeActions(topRange);
06156 }
06157 
06158 const QList< KTextEditor::SmartRange * > KateDocument::viewActions( KTextEditor::View * view ) const
06159 {
06160   return static_cast<KateView*>(view)->actions();
06161 }
06162 
06163 void KateDocument::attributeDynamic( KTextEditor::Attribute::Ptr )
06164 {
06165   // TODO link in with cursor + mouse tracking
06166 }
06167 
06168 void KateDocument::attributeNotDynamic( KTextEditor::Attribute::Ptr )
06169 {
06170   // TODO de-link cursor + mouse tracking
06171 }
06172 
06173 void KateDocument::clearSmartInterface( )
06174 {
06175   clearDocumentHighlights();
06176   foreach (KateView* view, m_views)
06177     clearViewHighlights(view);
06178 
06179   clearDocumentActions();
06180 
06181   m_smartManager->clear(false);
06182 }
06183 
06184 void KateDocument::deleteCursors( )
06185 {
06186   m_smartManager->deleteCursors(false);
06187 }
06188 
06189 void KateDocument::deleteRanges( )
06190 {
06191   m_smartManager->deleteRanges(false);
06192 }
06193 
06194 void KateDocument::clearDocumentHighlights( )
06195 {
06196   m_documentHighlights.clear();
06197 }
06198 
06199 void KateDocument::clearViewHighlights( KTextEditor::View * view )
06200 {
06201   static_cast<KateView*>(view)->clearExternalHighlights();
06202 }
06203 
06204 void KateDocument::clearDocumentActions( )
06205 {
06206   m_documentActions.clear();
06207 }
06208 
06209 void KateDocument::clearViewActions( KTextEditor::View * view )
06210 {
06211   static_cast<KateView*>(view)->clearActions();
06212 }
06213 
06214 void KateDocument::ignoreModifiedOnDiskOnce( )
06215 {
06216   m_isasking = -1;
06217 }
06218 
06219 KateHighlighting * KateDocument::highlight( ) const
06220 {
06221   return m_buffer->highlight();
06222 }
06223 
06224 uint KateDocument::getRealLine( unsigned int virtualLine )
06225 {
06226   return m_buffer->lineNumber (virtualLine);
06227 }
06228 
06229 uint KateDocument::getVirtualLine( unsigned int realLine )
06230 {
06231   return m_buffer->lineVisibleNumber (realLine);
06232 }
06233 
06234 uint KateDocument::visibleLines( )
06235 {
06236   return m_buffer->countVisible ();
06237 }
06238 
06239 KateTextLine::Ptr KateDocument::kateTextLine( uint i )
06240 {
06241   return m_buffer->line (i);
06242 }
06243 
06244 KateTextLine::Ptr KateDocument::plainKateTextLine( uint i )
06245 {
06246   return m_buffer->plainLine (i);
06247 }
06248 
06249 bool KateDocument::undoDontMerge( ) const
06250 {
06251   return m_undoDontMerge;
06252 }
06253 
06254 void KateDocument::setUndoDontMergeComplex(bool dontMerge)
06255 {
06256   m_undoComplexMerge = dontMerge;
06257 }
06258 
06259 bool KateDocument::undoDontMergeComplex( ) const
06260 {
06261   return m_undoComplexMerge;
06262 }
06263 
06264 void KateDocument::setUndoDontMerge(bool dontMerge)
06265 {
06266   m_undoDontMerge = dontMerge;
06267 }
06268 
06269 bool KateDocument::isEditRunning() const
06270 {
06271   return editIsRunning;
06272 }
06273 
06274 void KateDocument::rangeDeleted( KTextEditor::SmartRange * range )
06275 {
06276   removeHighlightFromDocument(range);
06277   removeActionsFromDocument(range);
06278 }
06279 
06280 //END KTextEditor::SmartInterface
06281 
06282 // kate: space-indent on; indent-width 2; replace-tabs on;
06283 
06284 bool KateDocument::simpleMode ()
06285 {
06286   return KateGlobal::self()->simpleMode () && KateGlobal::self()->documentConfig()->allowSimpleMode ();
06287 }
06288 
06289 KateDocument::LoadSaveFilterCheckPlugins* KateDocument::loadSaveFilterCheckPlugins()
06290 {
06291   K_GLOBAL_STATIC(KateDocument::LoadSaveFilterCheckPlugins, s_loadSaveFilterCheckPlugins)
06292   return s_loadSaveFilterCheckPlugins;
06293 }
06294 
06295 //BEGIN KTextEditor::AnnotationInterface
06296 void KateDocument::setAnnotationModel( KTextEditor::AnnotationModel* model )
06297 {
06298   KTextEditor::AnnotationModel* oldmodel = m_annotationModel;
06299   m_annotationModel = model;
06300   emit annotationModelChanged(oldmodel, m_annotationModel);
06301 }
06302 
06303 KTextEditor::AnnotationModel* KateDocument::annotationModel() const
06304 {
06305   return m_annotationModel;
06306 }
06307 //END KTextEditor::AnnotationInterface
06308 
06309 //TAKEN FROM kparts.h
06310 bool KateDocument::queryClose()
06311 {
06312     if ( !isReadWrite() || !isModified() )
06313         return true;
06314 
06315     QString docName = documentName();
06316 
06317     QWidget *parentWidget=widget();
06318     if(!parentWidget) parentWidget=QApplication::activeWindow();
06319 
06320     int res = KMessageBox::warningYesNoCancel( parentWidget,
06321                                                i18n( "The document \"%1\" has been modified.\n"
06322                                                      "Do you want to save your changes or discard them?" ,  docName ),
06323                                                i18n( "Close Document" ), KStandardGuiItem::save(), KStandardGuiItem::discard() );
06324 
06325     bool abortClose=false;
06326     bool handled=false;
06327 
06328     switch(res) {
06329     case KMessageBox::Yes :
06330         sigQueryClose(&handled,&abortClose);
06331         if (!handled)
06332         {
06333             if (url().isEmpty())
06334             {
06335                 KUrl url = KFileDialog::getSaveUrl(KUrl(), QString(), parentWidget);
06336                 if (url.isEmpty())
06337                     return false;
06338 
06339                 saveAs( url );
06340             }
06341             else
06342             {
06343                 save();
06344             }
06345         } else if (abortClose) return false;
06346         return waitSaveComplete();
06347     case KMessageBox::No :
06348         return true;
06349     default : // case KMessageBox::Cancel :
06350         return false;
06351     }
06352 }
06353 
06354 // Kill our helpers again
06355 #ifdef FAST_DEBUG_ENABLE
06356 # undef FAST_DEBUG_ENABLE
06357 #endif
06358 #undef FAST_DEBUG
06359 

Kate

Skip menu "Kate"
  • Main Page
  • Namespace List
  • Class Hierarchy
  • Alphabetical List
  • Class List
  • File List
  • Namespace Members
  • Class Members
  • Related Pages

kdelibs

Skip menu "kdelibs"
  • DNSSD
  • Interfaces
  •   KHexEdit
  •   KMediaPlayer
  •   KSpeech
  •   KTextEditor
  • Kate
  • kconf_update
  • KDE3Support
  •   KUnitTest
  • KDECore
  • KDED
  • KDEsu
  • KDEUI
  • KDocTools
  • KFile
  • KHTML
  • KImgIO
  • KInit
  • KIO
  • KIOSlave
  • KJS
  •   KJS-API
  •   WTF
  • kjsembed
  • KNewStuff
  • KParts
  • Kross
  • KUtils
  • Nepomuk
  • Solid
  • Sonnet
  • ThreadWeaver
Generated for kdelibs by doxygen 1.5.4
This website is maintained by Adriaan de Groot and Allen Winter.
KDE® and the K Desktop Environment® logo are registered trademarks of KDE e.V. | Legal