diff options
Diffstat (limited to 'kate/part/katedocument.cpp')
-rw-r--r-- | kate/part/katedocument.cpp | 5174 |
1 files changed, 5174 insertions, 0 deletions
diff --git a/kate/part/katedocument.cpp b/kate/part/katedocument.cpp new file mode 100644 index 000000000..68374ebe1 --- /dev/null +++ b/kate/part/katedocument.cpp @@ -0,0 +1,5174 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001-2004 Christoph Cullmann <cullmann@kde.org> + Copyright (C) 2001 Joseph Wenninger <jowenn@kde.org> + Copyright (C) 1999 Jochen Wilhelmy <digisnap@cs.tu-berlin.de> + + This library is free software; you can redistribute it and/or + modify it under the terms of the GNU Library General Public + License version 2 as published by the Free Software Foundation. + + This library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Library General Public License for more details. + + You should have received a copy of the GNU Library General Public License + along with this library; see the file COPYING.LIB. If not, write to + the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, + Boston, MA 02111-13020, USA. +*/ + +//BEGIN includes +#include "katedocument.h" +#include "katedocument.moc" +#include "katekeyinterceptorfunctor.h" +#include "katefactory.h" +#include "katedialogs.h" +#include "katehighlight.h" +#include "kateview.h" +#include "katesearch.h" +#include "kateautoindent.h" +#include "katetextline.h" +#include "katedocumenthelpers.h" +#include "kateprinter.h" +#include "katelinerange.h" +#include "katesupercursor.h" +#include "katearbitraryhighlight.h" +#include "katerenderer.h" +#include "kateattribute.h" +#include "kateconfig.h" +#include "katefiletype.h" +#include "kateschema.h" +#include "katetemplatehandler.h" +#include <ktexteditor/plugin.h> + +#include <kio/job.h> +#include <kio/netaccess.h> +#include <kio/kfileitem.h> + + +#include <kparts/event.h> + +#include <klocale.h> +#include <kglobal.h> +#include <kapplication.h> +#include <kpopupmenu.h> +#include <kconfig.h> +#include <kfiledialog.h> +#include <kmessagebox.h> +#include <kstdaction.h> +#include <kiconloader.h> +#include <kxmlguifactory.h> +#include <kdialogbase.h> +#include <kdebug.h> +#include <kglobalsettings.h> +#include <klibloader.h> +#include <kdirwatch.h> +#include <kwin.h> +#include <kencodingfiledialog.h> +#include <ktempfile.h> +#include <kmdcodec.h> +#include <kstandarddirs.h> + +#include <qtimer.h> +#include <qfile.h> +#include <qclipboard.h> +#include <qtextstream.h> +#include <qtextcodec.h> +#include <qmap.h> +//END includes + +//BEGIN PRIVATE CLASSES +class KatePartPluginItem +{ + public: + KTextEditor::Plugin *plugin; +}; +//END PRIVATE CLASSES + +//BEGIN d'tor, c'tor +// +// KateDocument Constructor +// +KateDocument::KateDocument ( bool bSingleViewMode, bool bBrowserView, + bool bReadOnly, QWidget *parentWidget, + const char *widgetName, QObject *parent, const char *name) +: Kate::Document(parent, name), + m_plugins (KateFactory::self()->plugins().count()), + m_undoDontMerge(false), + m_undoIgnoreCancel(false), + lastUndoGroupWhenSaved( 0 ), + lastRedoGroupWhenSaved( 0 ), + docWasSavedWhenUndoWasEmpty( true ), + docWasSavedWhenRedoWasEmpty( true ), + m_modOnHd (false), + m_modOnHdReason (0), + m_job (0), + m_tempFile (0), + m_tabInterceptor(0) +{ + m_undoComplexMerge=false; + m_isInUndo = false; + // my dcop object + setObjId ("KateDocument#"+documentDCOPSuffix()); + + // ktexteditor interfaces + setBlockSelectionInterfaceDCOPSuffix (documentDCOPSuffix()); + setConfigInterfaceDCOPSuffix (documentDCOPSuffix()); + setConfigInterfaceExtensionDCOPSuffix (documentDCOPSuffix()); + setCursorInterfaceDCOPSuffix (documentDCOPSuffix()); + setEditInterfaceDCOPSuffix (documentDCOPSuffix()); + setEncodingInterfaceDCOPSuffix (documentDCOPSuffix()); + setHighlightingInterfaceDCOPSuffix (documentDCOPSuffix()); + setMarkInterfaceDCOPSuffix (documentDCOPSuffix()); + setMarkInterfaceExtensionDCOPSuffix (documentDCOPSuffix()); + setPrintInterfaceDCOPSuffix (documentDCOPSuffix()); + setSearchInterfaceDCOPSuffix (documentDCOPSuffix()); + setSelectionInterfaceDCOPSuffix (documentDCOPSuffix()); + setSelectionInterfaceExtDCOPSuffix (documentDCOPSuffix()); + setSessionConfigInterfaceDCOPSuffix (documentDCOPSuffix()); + setUndoInterfaceDCOPSuffix (documentDCOPSuffix()); + setWordWrapInterfaceDCOPSuffix (documentDCOPSuffix()); + + // init local plugin array + m_plugins.fill (0); + + // register doc at factory + KateFactory::self()->registerDocument (this); + + m_reloading = false; + m_loading = false; + m_encodingSticky = false; + + m_buffer = new KateBuffer (this); + + // init the config object, be careful not to use it + // until the initial readConfig() call is done + m_config = new KateDocumentConfig (this); + + // init some more vars ! + m_activeView = 0L; + + hlSetByUser = false; + m_fileType = -1; + m_fileTypeSetByUser = false; + setInstance( KateFactory::self()->instance() ); + + editSessionNumber = 0; + editIsRunning = false; + m_editCurrentUndo = 0L; + editWithUndo = false; + + m_docNameNumber = 0; + + m_bSingleViewMode = bSingleViewMode; + m_bBrowserView = bBrowserView; + m_bReadOnly = bReadOnly; + + m_marks.setAutoDelete( true ); + m_markPixmaps.setAutoDelete( true ); + m_markDescriptions.setAutoDelete( true ); + setMarksUserChangable( markType01 ); + + m_undoMergeTimer = new QTimer(this); + connect(m_undoMergeTimer, SIGNAL(timeout()), SLOT(undoCancel())); + + clearMarks (); + clearUndo (); + clearRedo (); + setModified (false); + docWasSavedWhenUndoWasEmpty = true; + + // normal hl + m_buffer->setHighlight (0); + + m_extension = new KateBrowserExtension( this ); + m_arbitraryHL = new KateArbitraryHighlight(); + m_indenter = KateAutoIndent::createIndenter ( this, 0 ); + + m_indenter->updateConfig (); + + // some nice signals from the buffer + connect(m_buffer, SIGNAL(tagLines(int,int)), this, SLOT(tagLines(int,int))); + connect(m_buffer, SIGNAL(codeFoldingUpdated()),this,SIGNAL(codeFoldingUpdated())); + + // if the user changes the highlight with the dialog, notify the doc + connect(KateHlManager::self(),SIGNAL(changed()),SLOT(internalHlChanged())); + + // signal for the arbitrary HL + connect(m_arbitraryHL, SIGNAL(tagLines(KateView*, KateSuperRange*)), SLOT(tagArbitraryLines(KateView*, KateSuperRange*))); + + // signals for mod on hd + connect( KateFactory::self()->dirWatch(), SIGNAL(dirty (const QString &)), + this, SLOT(slotModOnHdDirty (const QString &)) ); + + connect( KateFactory::self()->dirWatch(), SIGNAL(created (const QString &)), + this, SLOT(slotModOnHdCreated (const QString &)) ); + + connect( KateFactory::self()->dirWatch(), SIGNAL(deleted (const QString &)), + this, SLOT(slotModOnHdDeleted (const QString &)) ); + + // update doc name + setDocName (""); + + // if single view mode, like in the konqui embedding, create a default view ;) + if ( m_bSingleViewMode ) + { + KTextEditor::View *view = createView( parentWidget, widgetName ); + insertChildClient( view ); + view->show(); + setWidget( view ); + } + + connect(this,SIGNAL(sigQueryClose(bool *, bool*)),this,SLOT(slotQueryClose_save(bool *, bool*))); + + m_isasking = 0; + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + { + if (config()->plugin (i)) + loadPlugin (i); + } +} + +// +// KateDocument Destructor +// +KateDocument::~KateDocument() +{ + // remove file from dirwatch + deactivateDirWatch (); + + if (!singleViewMode()) + { + // clean up remaining views + m_views.setAutoDelete( true ); + m_views.clear(); + } + + delete m_editCurrentUndo; + + delete m_arbitraryHL; + + // cleanup the undo items, very important, truee :/ + undoItems.setAutoDelete(true); + undoItems.clear(); + + // clean up plugins + unloadAllPlugins (); + + delete m_config; + delete m_indenter; + KateFactory::self()->deregisterDocument (this); +} +//END + +//BEGIN Plugins +void KateDocument::unloadAllPlugins () +{ + for (uint i=0; i<m_plugins.count(); i++) + unloadPlugin (i); +} + +void KateDocument::enableAllPluginsGUI (KateView *view) +{ + for (uint i=0; i<m_plugins.count(); i++) + enablePluginGUI (m_plugins[i], view); +} + +void KateDocument::disableAllPluginsGUI (KateView *view) +{ + for (uint i=0; i<m_plugins.count(); i++) + disablePluginGUI (m_plugins[i], view); +} + +void KateDocument::loadPlugin (uint pluginIndex) +{ + if (m_plugins[pluginIndex]) return; + + m_plugins[pluginIndex] = KTextEditor::createPlugin (QFile::encodeName((KateFactory::self()->plugins())[pluginIndex]->library()), this); + + enablePluginGUI (m_plugins[pluginIndex]); +} + +void KateDocument::unloadPlugin (uint pluginIndex) +{ + if (!m_plugins[pluginIndex]) return; + + disablePluginGUI (m_plugins[pluginIndex]); + + delete m_plugins[pluginIndex]; + m_plugins[pluginIndex] = 0L; +} + +void KateDocument::enablePluginGUI (KTextEditor::Plugin *plugin, KateView *view) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + KXMLGUIFactory *factory = view->factory(); + if ( factory ) + factory->removeClient( view ); + + KTextEditor::pluginViewInterface(plugin)->addView(view); + + if ( factory ) + factory->addClient( view ); +} + +void KateDocument::enablePluginGUI (KTextEditor::Plugin *plugin) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + for (uint i=0; i< m_views.count(); i++) + enablePluginGUI (plugin, m_views.at(i)); +} + +void KateDocument::disablePluginGUI (KTextEditor::Plugin *plugin, KateView *view) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + KXMLGUIFactory *factory = view->factory(); + if ( factory ) + factory->removeClient( view ); + + KTextEditor::pluginViewInterface( plugin )->removeView( view ); + + if ( factory ) + factory->addClient( view ); +} + +void KateDocument::disablePluginGUI (KTextEditor::Plugin *plugin) +{ + if (!plugin) return; + if (!KTextEditor::pluginViewInterface(plugin)) return; + + for (uint i=0; i< m_views.count(); i++) + disablePluginGUI (plugin, m_views.at(i)); +} +//END + +//BEGIN KTextEditor::Document stuff + +KTextEditor::View *KateDocument::createView( QWidget *parent, const char *name ) +{ + KateView* newView = new KateView( this, parent, name); + connect(newView, SIGNAL(cursorPositionChanged()), SLOT(undoCancel())); + if ( s_fileChangedDialogsActivated ) + connect( newView, SIGNAL(gotFocus( Kate::View * )), this, SLOT(slotModifiedOnDisk()) ); + return newView; +} + +QPtrList<KTextEditor::View> KateDocument::views () const +{ + return m_textEditViews; +} + +void KateDocument::setActiveView( KateView *view ) +{ + if ( m_activeView == view ) return; + + m_activeView = view; +} +//END + +//BEGIN KTextEditor::ConfigInterfaceExtension stuff + +uint KateDocument::configPages () const +{ + return 10; +} + +KTextEditor::ConfigPage *KateDocument::configPage (uint number, QWidget *parent, const char * ) +{ + switch( number ) + { + case 0: + return new KateViewDefaultsConfig (parent); + + case 1: + return new KateSchemaConfigPage (parent, this); + + case 2: + return new KateSelectConfigTab (parent); + + case 3: + return new KateEditConfigTab (parent); + + case 4: + return new KateIndentConfigTab (parent); + + case 5: + return new KateSaveConfigTab (parent); + + case 6: + return new KateHlConfigPage (parent, this); + + case 7: + return new KateFileTypeConfigTab (parent); + + case 8: + return new KateEditKeyConfiguration (parent, this); + + case 9: + return new KatePartPluginConfigPage (parent); + + default: + return 0; + } + + return 0; +} + +QString KateDocument::configPageName (uint number) const +{ + switch( number ) + { + case 0: + return i18n ("Appearance"); + + case 1: + return i18n ("Fonts & Colors"); + + case 2: + return i18n ("Cursor & Selection"); + + case 3: + return i18n ("Editing"); + + case 4: + return i18n ("Indentation"); + + case 5: + return i18n("Open/Save"); + + case 6: + return i18n ("Highlighting"); + + case 7: + return i18n("Filetypes"); + + case 8: + return i18n ("Shortcuts"); + + case 9: + return i18n ("Plugins"); + + default: + return QString (""); + } + + return QString (""); +} + +QString KateDocument::configPageFullName (uint number) const +{ + switch( number ) + { + case 0: + return i18n("Appearance"); + + case 1: + return i18n ("Font & Color Schemas"); + + case 2: + return i18n ("Cursor & Selection Behavior"); + + case 3: + return i18n ("Editing Options"); + + case 4: + return i18n ("Indentation Rules"); + + case 5: + return i18n("File Opening & Saving"); + + case 6: + return i18n ("Highlighting Rules"); + + case 7: + return i18n("Filetype Specific Settings"); + + case 8: + return i18n ("Shortcuts Configuration"); + + case 9: + return i18n ("Plugin Manager"); + + default: + return QString (""); + } + + return QString (""); +} + +QPixmap KateDocument::configPagePixmap (uint number, int size) const +{ + switch( number ) + { + case 0: + return BarIcon("view_text",size); + + case 1: + return BarIcon("colorize", size); + + case 2: + return BarIcon("frame_edit", size); + + case 3: + return BarIcon("edit", size); + + case 4: + return BarIcon("rightjust", size); + + case 5: + return BarIcon("filesave", size); + + case 6: + return BarIcon("source", size); + + case 7: + return BarIcon("edit", size); + + case 8: + return BarIcon("key_enter", size); + + case 9: + return BarIcon("connect_established", size); + + default: + return BarIcon("edit", size); + } + + return BarIcon("edit", size); +} +//END + +//BEGIN KTextEditor::EditInterface stuff + +QString KateDocument::text() const +{ + QString s; + + for (uint i = 0; i < m_buffer->count(); i++) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(i); + + if (textLine) + { + s.append (textLine->string()); + + if ((i+1) < m_buffer->count()) + s.append('\n'); + } + } + + return s; +} + +QString KateDocument::text ( uint startLine, uint startCol, uint endLine, uint endCol ) const +{ + return text(startLine, startCol, endLine, endCol, false); +} + +QString KateDocument::text ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise) const +{ + if ( blockwise && (startCol > endCol) ) + return QString (); + + QString s; + + if (startLine == endLine) + { + if (startCol > endCol) + return QString (); + + KateTextLine::Ptr textLine = m_buffer->plainLine(startLine); + + if ( !textLine ) + return QString (); + + return textLine->string(startCol, endCol-startCol); + } + else + { + + for (uint i = startLine; (i <= endLine) && (i < m_buffer->count()); i++) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(i); + + if ( !blockwise ) + { + if (i == startLine) + s.append (textLine->string(startCol, textLine->length()-startCol)); + else if (i == endLine) + s.append (textLine->string(0, endCol)); + else + s.append (textLine->string()); + } + else + { + s.append( textLine->string( startCol, endCol-startCol)); + } + + if ( i < endLine ) + s.append('\n'); + } + } + + return s; +} + +QString KateDocument::textLine( uint line ) const +{ + KateTextLine::Ptr l = m_buffer->plainLine(line); + + if (!l) + return QString(); + + return l->string(); +} + +bool KateDocument::setText(const QString &s) +{ + if (!isReadWrite()) + return false; + + QPtrList<KTextEditor::Mark> m = marks (); + QValueList<KTextEditor::Mark> msave; + + for (uint i=0; i < m.count(); i++) + msave.append (*m.at(i)); + + editStart (); + + // delete the text + clear(); + + // insert the new text + insertText (0, 0, s); + + editEnd (); + + for (uint i=0; i < msave.count(); i++) + setMark (msave[i].line, msave[i].type); + + return true; +} + +bool KateDocument::clear() +{ + if (!isReadWrite()) + return false; + + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) { + view->clear(); + view->tagAll(); + view->update(); + } + + clearMarks (); + + return removeText (0,0,lastLine()+1, 0); +} + +bool KateDocument::insertText( uint line, uint col, const QString &s) +{ + return insertText (line, col, s, false); +} + +bool KateDocument::insertText( uint line, uint col, const QString &s, bool blockwise ) +{ + if (!isReadWrite()) + return false; + + if (s.isEmpty()) + return true; + + if (line == numLines()) + editInsertLine(line,""); + else if (line > lastLine()) + return false; + + editStart (); + + uint insertPos = col; + uint len = s.length(); + + QString buf; + + bool replacetabs = ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn && ! m_isInUndo ); + uint tw = config()->tabWidth(); + uint insertPosExpanded = insertPos; + KateTextLine::Ptr l = m_buffer->line( line ); + if (l != 0) + insertPosExpanded = l->cursorX( insertPos, tw ); + + for (uint pos = 0; pos < len; pos++) + { + QChar ch = s[pos]; + + if (ch == '\n') + { + editInsertText (line, insertPos, buf); + + if ( !blockwise ) + { + editWrapLine (line, insertPos + buf.length()); + insertPos = insertPosExpanded = 0; + } + else + { + if ( line == lastLine() ) + editWrapLine (line, insertPos + buf.length()); + } + + line++; + buf.truncate(0); + l = m_buffer->line( line ); + if (l) + insertPosExpanded = l->cursorX( insertPos, tw ); + } + else + { + if ( replacetabs && ch == '\t' ) + { + uint tr = tw - ( insertPosExpanded+buf.length() )%tw; + for ( uint i=0; i < tr; i++ ) + buf += ' '; + } + else + buf += ch; // append char to buffer + } + } + + editInsertText (line, insertPos, buf); + + editEnd (); + emit textInserted(line,insertPos); + return true; +} + +bool KateDocument::removeText ( uint startLine, uint startCol, uint endLine, uint endCol ) +{ + return removeText (startLine, startCol, endLine, endCol, false); +} + +bool KateDocument::removeText ( uint startLine, uint startCol, uint endLine, uint endCol, bool blockwise) +{ + if (!isReadWrite()) + return false; + + if ( blockwise && (startCol > endCol) ) + return false; + + if ( startLine > endLine ) + return false; + + if ( startLine > lastLine() ) + return false; + + if (!blockwise) { + emit aboutToRemoveText(KateTextRange(startLine,startCol,endLine,endCol)); + } + editStart (); + + if ( !blockwise ) + { + if ( endLine > lastLine() ) + { + endLine = lastLine()+1; + endCol = 0; + } + + if (startLine == endLine) + { + editRemoveText (startLine, startCol, endCol-startCol); + } + else if ((startLine+1) == endLine) + { + if ( (m_buffer->plainLine(startLine)->length()-startCol) > 0 ) + editRemoveText (startLine, startCol, m_buffer->plainLine(startLine)->length()-startCol); + + editRemoveText (startLine+1, 0, endCol); + editUnWrapLine (startLine); + } + else + { + for (uint line = endLine; line >= startLine; line--) + { + if ((line > startLine) && (line < endLine)) + { + editRemoveLine (line); + } + else + { + if (line == endLine) + { + if ( endLine <= lastLine() ) + editRemoveText (line, 0, endCol); + } + else + { + if ( (m_buffer->plainLine(line)->length()-startCol) > 0 ) + editRemoveText (line, startCol, m_buffer->plainLine(line)->length()-startCol); + + editUnWrapLine (startLine); + } + } + + if ( line == 0 ) + break; + } + } + } // if ( ! blockwise ) + else + { + if ( endLine > lastLine() ) + endLine = lastLine (); + + for (uint line = endLine; line >= startLine; line--) + { + + editRemoveText (line, startCol, endCol-startCol); + + if ( line == 0 ) + break; + } + } + + editEnd (); + emit textRemoved(); + return true; +} + +bool KateDocument::insertLine( uint l, const QString &str ) +{ + if (!isReadWrite()) + return false; + + if (l > numLines()) + return false; + + return editInsertLine (l, str); +} + +bool KateDocument::removeLine( uint line ) +{ + if (!isReadWrite()) + return false; + + if (line > lastLine()) + return false; + + return editRemoveLine (line); +} + +uint KateDocument::length() const +{ + uint l = 0; + + for (uint i = 0; i < m_buffer->count(); i++) + { + KateTextLine::Ptr line = m_buffer->plainLine(i); + + if (line) + l += line->length(); + } + + return l; +} + +uint KateDocument::numLines() const +{ + return m_buffer->count(); +} + +uint KateDocument::numVisLines() const +{ + return m_buffer->countVisible (); +} + +int KateDocument::lineLength ( uint line ) const +{ + KateTextLine::Ptr l = m_buffer->plainLine(line); + + if (!l) + return -1; + + return l->length(); +} +//END + +//BEGIN KTextEditor::EditInterface internal stuff +// +// Starts an edit session with (or without) undo, update of view disabled during session +// +void KateDocument::editStart (bool withUndo) +{ + editSessionNumber++; + + if (editSessionNumber > 1) + return; + + editIsRunning = true; + editWithUndo = withUndo; + + if (editWithUndo) + undoStart(); + else + undoCancel(); + + for (uint z = 0; z < m_views.count(); z++) + { + m_views.at(z)->editStart (); + } + + m_buffer->editStart (); +} + +void KateDocument::undoStart() +{ + if (m_editCurrentUndo || (m_activeView && m_activeView->imComposeEvent())) return; + + // Make sure the buffer doesn't get bigger than requested + if ((config()->undoSteps() > 0) && (undoItems.count() > config()->undoSteps())) + { + undoItems.setAutoDelete(true); + undoItems.removeFirst(); + undoItems.setAutoDelete(false); + docWasSavedWhenUndoWasEmpty = false; + } + + // new current undo item + m_editCurrentUndo = new KateUndoGroup(this); +} + +void KateDocument::undoEnd() +{ + if (m_activeView && m_activeView->imComposeEvent()) + return; + + if (m_editCurrentUndo) + { + bool changedUndo = false; + + if (m_editCurrentUndo->isEmpty()) + delete m_editCurrentUndo; + else if (!m_undoDontMerge && undoItems.last() && undoItems.last()->merge(m_editCurrentUndo,m_undoComplexMerge)) + delete m_editCurrentUndo; + else + { + undoItems.append(m_editCurrentUndo); + changedUndo = true; + } + + m_undoDontMerge = false; + m_undoIgnoreCancel = true; + + m_editCurrentUndo = 0L; + + // (Re)Start the single-shot timer to cancel the undo merge + // the user has 5 seconds to input more data, or undo merging gets canceled for the current undo item. + m_undoMergeTimer->start(5000, true); + + if (changedUndo) + emit undoChanged(); + } +} + +void KateDocument::undoCancel() +{ + if (m_undoIgnoreCancel) { + m_undoIgnoreCancel = false; + return; + } + + m_undoDontMerge = true; + + Q_ASSERT(!m_editCurrentUndo); + + // As you can see by the above assert, neither of these should really be required + delete m_editCurrentUndo; + m_editCurrentUndo = 0L; +} + +void KateDocument::undoSafePoint() { + Q_ASSERT(m_editCurrentUndo); + if (!m_editCurrentUndo) return; + m_editCurrentUndo->safePoint(); +} + +// +// End edit session and update Views +// +void KateDocument::editEnd () +{ + if (editSessionNumber == 0) + return; + + // wrap the new/changed text, if something really changed! + if (m_buffer->editChanged() && (editSessionNumber == 1)) + if (editWithUndo && config()->wordWrap()) + wrapText (m_buffer->editTagStart(), m_buffer->editTagEnd()); + + editSessionNumber--; + + if (editSessionNumber > 0) + return; + + // end buffer edit, will trigger hl update + // this will cause some possible adjustment of tagline start/end + m_buffer->editEnd (); + + if (editWithUndo) + undoEnd(); + + // edit end for all views !!!!!!!!! + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->editEnd (m_buffer->editTagStart(), m_buffer->editTagEnd(), m_buffer->editTagFrom()); + + if (m_buffer->editChanged()) + { + setModified(true); + emit textChanged (); + } + + editIsRunning = false; +} + +bool KateDocument::wrapText (uint startLine, uint endLine) +{ + uint col = config()->wordWrapAt(); + + if (col == 0) + return false; + + editStart (); + + for (uint line = startLine; (line <= endLine) && (line < numLines()); line++) + { + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + kdDebug (13020) << "try wrap line: " << line << endl; + + if (l->lengthWithTabs(m_buffer->tabWidth()) > col) + { + KateTextLine::Ptr nextl = m_buffer->line(line+1); + + kdDebug (13020) << "do wrap line: " << line << endl; + + const QChar *text = l->text(); + uint eolPosition = l->length()-1; + + // take tabs into account here, too + uint x = 0; + const QString & t = l->string(); + uint z2 = 0; + for ( ; z2 < l->length(); z2++) + { + if (t[z2] == QChar('\t')) + x += m_buffer->tabWidth() - (x % m_buffer->tabWidth()); + else + x++; + + if (x > col) + break; + } + + uint searchStart = kMin (z2, l->length()-1); + + // If where we are wrapping is an end of line and is a space we don't + // want to wrap there + if (searchStart == eolPosition && text[searchStart].isSpace()) + searchStart--; + + // Scan backwards looking for a place to break the line + // We are not interested in breaking at the first char + // of the line (if it is a space), but we are at the second + // anders: if we can't find a space, try breaking on a word + // boundry, using KateHighlight::canBreakAt(). + // This could be a priority (setting) in the hl/filetype/document + int z = 0; + uint nw = 0; // alternative position, a non word character + for (z=searchStart; z > 0; z--) + { + if (text[z].isSpace()) break; + if ( ! nw && highlight()->canBreakAt( text[z] , l->attribute(z) ) ) + nw = z; + } + + if (z > 0) + { + // cu space + editRemoveText (line, z, 1); + } + else + { + // There was no space to break at so break at a nonword character if + // found, or at the wrapcolumn ( that needs be configurable ) + // Don't try and add any white space for the break + if ( nw && nw < col ) nw++; // break on the right side of the character + z = nw ? nw : col; + } + + if (nextl && !nextl->isAutoWrapped()) + { + editWrapLine (line, z, true); + editMarkLineAutoWrapped (line+1, true); + + endLine++; + } + else + { + if (nextl && (nextl->length() > 0) && !nextl->getChar(0).isSpace() && ((l->length() < 1) || !l->getChar(l->length()-1).isSpace())) + editInsertText (line+1, 0, QString (" ")); + + bool newLineAdded = false; + editWrapLine (line, z, false, &newLineAdded); + + editMarkLineAutoWrapped (line+1, true); + + endLine++; + } + } + } + + editEnd (); + + return true; +} + +void KateDocument::editAddUndo (KateUndoGroup::UndoType type, uint line, uint col, uint len, const QString &text) +{ + if (editIsRunning && editWithUndo && m_editCurrentUndo) { + m_editCurrentUndo->addItem(type, line, col, len, text); + + // Clear redo buffer + if (redoItems.count()) { + redoItems.setAutoDelete(true); + redoItems.clear(); + redoItems.setAutoDelete(false); + } + } +} + +bool KateDocument::editInsertText ( uint line, uint col, const QString &str ) +{ + if (!isReadWrite()) + return false; + + QString s = str; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + if ( config()->configFlags() & KateDocumentConfig::cfReplaceTabsDyn && ! m_isInUndo ) + { + uint tw = config()->tabWidth(); + int pos = 0; + uint l = 0; + while ( (pos = s.find('\t')) > -1 ) + { + l = tw - ( (col + pos)%tw ); + s.replace( pos, 1, QString().fill( ' ', l ) ); + } + } + + editStart (); + + editAddUndo (KateUndoGroup::editInsertText, line, col, s.length(), s); + + l->insertText (col, s.length(), s.unicode()); +// removeTrailingSpace(line); // ### nessecary? + + m_buffer->changeLine(line); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editTextInserted (line, col, s.length()); + + editEnd (); + + return true; +} + +bool KateDocument::editRemoveText ( uint line, uint col, uint len ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editRemoveText, line, col, len, l->string().mid(col, len)); + + l->removeText (col, len); + removeTrailingSpace( line ); + + m_buffer->changeLine(line); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editTextRemoved (line, col, len); + + editEnd (); + + return true; +} + +bool KateDocument::editMarkLineAutoWrapped ( uint line, bool autowrapped ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editMarkLineAutoWrapped, line, autowrapped ? 1 : 0, 0, QString::null); + + l->setAutoWrapped (autowrapped); + + m_buffer->changeLine(line); + + editEnd (); + + return true; +} + +bool KateDocument::editWrapLine ( uint line, uint col, bool newLine, bool *newLineAdded) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + + if (!l) + return false; + + editStart (); + + KateTextLine::Ptr nextLine = m_buffer->line(line+1); + + int pos = l->length() - col; + + if (pos < 0) + pos = 0; + + editAddUndo (KateUndoGroup::editWrapLine, line, col, pos, (!nextLine || newLine) ? "1" : "0"); + + if (!nextLine || newLine) + { + KateTextLine::Ptr textLine = new KateTextLine(); + + textLine->insertText (0, pos, l->text()+col, l->attributes()+col); + l->truncate(col); + + m_buffer->insertLine (line+1, textLine); + m_buffer->changeLine(line); + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line ) + { + if ((col == 0) || (it.current()->line > line)) + list.append( it.current() ); + } + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line++; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + // yes, we added a new line ! + if (newLineAdded) + (*newLineAdded) = true; + } + else + { + nextLine->insertText (0, pos, l->text()+col, l->attributes()+col); + l->truncate(col); + + m_buffer->changeLine(line); + m_buffer->changeLine(line+1); + + // no, no new line added ! + if (newLineAdded) + (*newLineAdded) = false; + } + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineWrapped (line, col, !nextLine || newLine); + + editEnd (); + + return true; +} + +bool KateDocument::editUnWrapLine ( uint line, bool removeLine, uint length ) +{ + if (!isReadWrite()) + return false; + + KateTextLine::Ptr l = m_buffer->line(line); + KateTextLine::Ptr nextLine = m_buffer->line(line+1); + + if (!l || !nextLine) + return false; + + editStart (); + + uint col = l->length (); + + editAddUndo (KateUndoGroup::editUnWrapLine, line, col, length, removeLine ? "1" : "0"); + + if (removeLine) + { + l->insertText (col, nextLine->length(), nextLine->text(), nextLine->attributes()); + + m_buffer->changeLine(line); + m_buffer->removeLine(line+1); + } + else + { + l->insertText (col, (nextLine->length() < length) ? nextLine->length() : length, + nextLine->text(), nextLine->attributes()); + nextLine->removeText (0, (nextLine->length() < length) ? nextLine->length() : length); + + m_buffer->changeLine(line); + m_buffer->changeLine(line+1); + } + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line+1 ) + list.append( it.current() ); + + if ( it.current()->line == line+1 ) + { + KTextEditor::Mark* mark = m_marks.take( line ); + + if (mark) + { + it.current()->type |= mark->type; + } + } + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line--; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineUnWrapped (line, col, removeLine, length); + + editEnd (); + + return true; +} + +bool KateDocument::editInsertLine ( uint line, const QString &s ) +{ + if (!isReadWrite()) + return false; + + if ( line > numLines() ) + return false; + + editStart (); + + editAddUndo (KateUndoGroup::editInsertLine, line, 0, s.length(), s); + + removeTrailingSpace( line ); // old line + + KateTextLine::Ptr tl = new KateTextLine(); + tl->insertText (0, s.length(), s.unicode(), 0); + m_buffer->insertLine(line, tl); + m_buffer->changeLine(line); + + removeTrailingSpace( line ); // new line + + QPtrList<KTextEditor::Mark> list; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if( it.current()->line >= line ) + list.append( it.current() ); + } + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line++; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineInserted (line); + + editEnd (); + + return true; +} + +bool KateDocument::editRemoveLine ( uint line ) +{ + if (!isReadWrite()) + return false; + + if ( line > lastLine() ) + return false; + + if ( numLines() == 1 ) + return editRemoveText (0, 0, m_buffer->line(0)->length()); + + editStart (); + + editAddUndo (KateUndoGroup::editRemoveLine, line, 0, lineLength(line), textLine(line)); + + m_buffer->removeLine(line); + + QPtrList<KTextEditor::Mark> list; + KTextEditor::Mark* rmark = 0; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + if ( (it.current()->line > line) ) + list.append( it.current() ); + else if ( (it.current()->line == line) ) + rmark = it.current(); + } + + if (rmark) + delete (m_marks.take (rmark->line)); + + for( QPtrListIterator<KTextEditor::Mark> it( list ); it.current(); ++it ) + { + KTextEditor::Mark* mark = m_marks.take( it.current()->line ); + mark->line--; + m_marks.insert( mark->line, mark ); + } + + if( !list.isEmpty() ) + emit marksChanged(); + + for( QPtrListIterator<KateSuperCursor> it (m_superCursors); it.current(); ++it ) + it.current()->editLineRemoved (line); + + editEnd(); + + return true; +} +//END + +//BEGIN KTextEditor::UndoInterface stuff + +uint KateDocument::undoCount () const +{ + return undoItems.count (); +} + +uint KateDocument::redoCount () const +{ + return redoItems.count (); +} + +uint KateDocument::undoSteps () const +{ + return m_config->undoSteps(); +} + +void KateDocument::setUndoSteps(uint steps) +{ + m_config->setUndoSteps (steps); +} + +void KateDocument::undo() +{ + m_isInUndo = true; + if ((undoItems.count() > 0) && undoItems.last()) + { + clearSelection (); + + undoItems.last()->undo(); + redoItems.append (undoItems.last()); + undoItems.removeLast (); + updateModified(); + + emit undoChanged (); + } + m_isInUndo = false; +} + +void KateDocument::redo() +{ + m_isInUndo = true; + if ((redoItems.count() > 0) && redoItems.last()) + { + clearSelection (); + + redoItems.last()->redo(); + undoItems.append (redoItems.last()); + redoItems.removeLast (); + updateModified(); + + emit undoChanged (); + } + m_isInUndo = false; +} + +void KateDocument::updateModified() +{ + /* + How this works: + + After noticing that there where to many scenarios to take into + consideration when using 'if's to toggle the "Modified" flag + I came up with this baby, flexible and repetitive calls are + minimal. + + A numeric unique pattern is generated by toggleing a set of bits, + each bit symbolizes a different state in the Undo Redo structure. + + undoItems.isEmpty() != null BIT 1 + redoItems.isEmpty() != null BIT 2 + docWasSavedWhenUndoWasEmpty == true BIT 3 + docWasSavedWhenRedoWasEmpty == true BIT 4 + lastUndoGroupWhenSavedIsLastUndo BIT 5 + lastUndoGroupWhenSavedIsLastRedo BIT 6 + lastRedoGroupWhenSavedIsLastUndo BIT 7 + lastRedoGroupWhenSavedIsLastRedo BIT 8 + + If you find a new pattern, please add it to the patterns array + */ + + unsigned char currentPattern = 0; + const unsigned char patterns[] = {5,16,24,26,88,90,93,133,144,149,165}; + const unsigned char patternCount = sizeof(patterns); + KateUndoGroup* undoLast = 0; + KateUndoGroup* redoLast = 0; + + if (undoItems.isEmpty()) + { + currentPattern |= 1; + } + else + { + undoLast = undoItems.last(); + } + + if (redoItems.isEmpty()) + { + currentPattern |= 2; + } + else + { + redoLast = redoItems.last(); + } + + if (docWasSavedWhenUndoWasEmpty) currentPattern |= 4; + if (docWasSavedWhenRedoWasEmpty) currentPattern |= 8; + if (lastUndoGroupWhenSaved == undoLast) currentPattern |= 16; + if (lastUndoGroupWhenSaved == redoLast) currentPattern |= 32; + if (lastRedoGroupWhenSaved == undoLast) currentPattern |= 64; + if (lastRedoGroupWhenSaved == redoLast) currentPattern |= 128; + + // This will print out the pattern information + + kdDebug(13020) << k_funcinfo + << "Pattern:" << static_cast<unsigned int>(currentPattern) << endl; + + for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) + { + if ( currentPattern == patterns[patternIndex] ) + { + setModified( false ); + kdDebug(13020) << k_funcinfo << "setting modified to false!" << endl; + break; + } + } +} + +void KateDocument::clearUndo() +{ + undoItems.setAutoDelete (true); + undoItems.clear (); + undoItems.setAutoDelete (false); + + lastUndoGroupWhenSaved = 0; + docWasSavedWhenUndoWasEmpty = false; + + emit undoChanged (); +} + +void KateDocument::clearRedo() +{ + redoItems.setAutoDelete (true); + redoItems.clear (); + redoItems.setAutoDelete (false); + + lastRedoGroupWhenSaved = 0; + docWasSavedWhenRedoWasEmpty = false; + + emit undoChanged (); +} + +QPtrList<KTextEditor::Cursor> KateDocument::cursors () const +{ + return myCursors; +} +//END + +//BEGIN KTextEditor::SearchInterface stuff + +bool KateDocument::searchText (unsigned int startLine, unsigned int startCol, const QString &text, unsigned int *foundAtLine, unsigned int *foundAtCol, unsigned int *matchLen, bool casesensitive, bool backwards) +{ + if (text.isEmpty()) + return false; + + int line = startLine; + int col = startCol; + + if (!backwards) + { + int searchEnd = lastLine(); + + while (line <= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, text, &foundAt, &myMatchLen, casesensitive, false); + + if (found) + { + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + col = 0; + line++; + } + } + else + { + // backward search + int searchEnd = 0; + + while (line >= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, text, &foundAt, &myMatchLen, casesensitive, true); + + if (found) + { + /* if ((uint) line == startLine && foundAt + myMatchLen >= (uint) col + && line == selectStart.line() && foundAt == (uint) selectStart.col() + && line == selectEnd.line() && foundAt + myMatchLen == (uint) selectEnd.col()) + { + // To avoid getting stuck at one match we skip a match if it is already + // selected (most likely because it has just been found). + if (foundAt > 0) + col = foundAt - 1; + else { + if (--line >= 0) + col = lineLength(line); + } + continue; + }*/ + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + if (line >= 1) + col = lineLength(line-1); + + line--; + } + } + + return false; +} + +bool KateDocument::searchText (unsigned int startLine, unsigned int startCol, const QRegExp ®exp, unsigned int *foundAtLine, unsigned int *foundAtCol, unsigned int *matchLen, bool backwards) +{ + kdDebug(13020)<<"KateDocument::searchText( "<<startLine<<", "<<startCol<<", "<<regexp.pattern()<<", "<<backwards<<" )"<<endl; + if (regexp.isEmpty() || !regexp.isValid()) + return false; + + int line = startLine; + int col = startCol; + + if (!backwards) + { + int searchEnd = lastLine(); + + while (line <= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, regexp, &foundAt, &myMatchLen, false); + + if (found) + { + // A special case which can only occur when searching with a regular expression consisting + // only of a lookahead (e.g. ^(?=\{) for a function beginning without selecting '{'). + if (myMatchLen == 0 && (uint) line == startLine && foundAt == (uint) col) + { + if (col < lineLength(line)) + col++; + else { + line++; + col = 0; + } + continue; + } + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + col = 0; + line++; + } + } + else + { + // backward search + int searchEnd = 0; + + while (line >= searchEnd) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + return false; + + uint foundAt, myMatchLen; + bool found = textLine->searchText (col, regexp, &foundAt, &myMatchLen, true); + + if (found) + { + /*if ((uint) line == startLine && foundAt + myMatchLen >= (uint) col + && line == selectStart.line() && foundAt == (uint) selectStart.col() + && line == selectEnd.line() && foundAt + myMatchLen == (uint) selectEnd.col()) + { + // To avoid getting stuck at one match we skip a match if it is already + // selected (most likely because it has just been found). + if (foundAt > 0) + col = foundAt - 1; + else { + if (--line >= 0) + col = lineLength(line); + } + continue; + }*/ + + (*foundAtLine) = line; + (*foundAtCol) = foundAt; + (*matchLen) = myMatchLen; + return true; + } + + if (line >= 1) + col = lineLength(line-1); + + line--; + } + } + + return false; +} +//END + +//BEGIN KTextEditor::HighlightingInterface stuff + +uint KateDocument::hlMode () +{ + return KateHlManager::self()->findHl(highlight()); +} + +bool KateDocument::setHlMode (uint mode) +{ + m_buffer->setHighlight (mode); + + if (true) + { + setDontChangeHlOnSave(); + return true; + } + + return false; +} + +void KateDocument::bufferHlChanged () +{ + // update all views + makeAttribs(false); + + emit hlChanged(); +} + +uint KateDocument::hlModeCount () +{ + return KateHlManager::self()->highlights(); +} + +QString KateDocument::hlModeName (uint mode) +{ + return KateHlManager::self()->hlName (mode); +} + +QString KateDocument::hlModeSectionName (uint mode) +{ + return KateHlManager::self()->hlSection (mode); +} + +void KateDocument::setDontChangeHlOnSave() +{ + hlSetByUser = true; +} +//END + +//BEGIN KTextEditor::ConfigInterface stuff +void KateDocument::readConfig(KConfig *config) +{ + config->setGroup("Kate Document Defaults"); + + // read max loadable blocks, more blocks will be swapped out + KateBuffer::setMaxLoadedBlocks (config->readNumEntry("Maximal Loaded Blocks", KateBuffer::maxLoadedBlocks())); + + KateDocumentConfig::global()->readConfig (config); + + config->setGroup("Kate View Defaults"); + KateViewConfig::global()->readConfig (config); + + config->setGroup("Kate Renderer Defaults"); + KateRendererConfig::global()->readConfig (config); +} + +void KateDocument::writeConfig(KConfig *config) +{ + config->setGroup("Kate Document Defaults"); + + // write max loadable blocks, more blocks will be swapped out + config->writeEntry("Maximal Loaded Blocks", KateBuffer::maxLoadedBlocks()); + + KateDocumentConfig::global()->writeConfig (config); + + config->setGroup("Kate View Defaults"); + KateViewConfig::global()->writeConfig (config); + + config->setGroup("Kate Renderer Defaults"); + KateRendererConfig::global()->writeConfig (config); +} + +void KateDocument::readConfig() +{ + KConfig *config = kapp->config(); + readConfig (config); +} + +void KateDocument::writeConfig() +{ + KConfig *config = kapp->config(); + writeConfig (config); + config->sync(); +} + +void KateDocument::readSessionConfig(KConfig *kconfig) +{ + // restore the url + KURL url (kconfig->readEntry("URL")); + + // get the encoding + QString tmpenc=kconfig->readEntry("Encoding"); + if (!tmpenc.isEmpty() && (tmpenc != encoding())) + setEncoding(tmpenc); + + // open the file if url valid + if (!url.isEmpty() && url.isValid()) + openURL (url); + + // restore the hl stuff + m_buffer->setHighlight(KateHlManager::self()->nameFind(kconfig->readEntry("Highlighting"))); + + if (hlMode() > 0) + hlSetByUser = true; + + // indent mode + config()->setIndentationMode( (uint)kconfig->readNumEntry("Indentation Mode", config()->indentationMode() ) ); + + // Restore Bookmarks + QValueList<int> marks = kconfig->readIntListEntry("Bookmarks"); + for( uint i = 0; i < marks.count(); i++ ) + addMark( marks[i], KateDocument::markType01 ); +} + +void KateDocument::writeSessionConfig(KConfig *kconfig) +{ + if ( m_url.isLocalFile() && !KGlobal::dirs()->relativeLocation("tmp", m_url.path()).startsWith("/")) + return; + // save url + kconfig->writeEntry("URL", m_url.prettyURL() ); + + // save encoding + kconfig->writeEntry("Encoding",encoding()); + + // save hl + kconfig->writeEntry("Highlighting", highlight()->name()); + + kconfig->writeEntry("Indentation Mode", config()->indentationMode() ); + + // Save Bookmarks + QValueList<int> marks; + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current() && it.current()->type & KTextEditor::MarkInterface::markType01; + ++it ) + marks << it.current()->line; + + kconfig->writeEntry( "Bookmarks", marks ); +} + +void KateDocument::configDialog() +{ + KDialogBase *kd = new KDialogBase ( KDialogBase::IconList, + i18n("Configure"), + KDialogBase::Ok | KDialogBase::Cancel | KDialogBase::Help, + KDialogBase::Ok, + kapp->mainWidget() ); + +#ifndef Q_WS_WIN //TODO: reenable + KWin::setIcons( kd->winId(), kapp->icon(), kapp->miniIcon() ); +#endif + + QPtrList<KTextEditor::ConfigPage> editorPages; + + for (uint i = 0; i < KTextEditor::configInterfaceExtension (this)->configPages (); i++) + { + QStringList path; + path.clear(); + path << KTextEditor::configInterfaceExtension (this)->configPageName (i); + QVBox *page = kd->addVBoxPage(path, KTextEditor::configInterfaceExtension (this)->configPageFullName (i), + KTextEditor::configInterfaceExtension (this)->configPagePixmap(i, KIcon::SizeMedium) ); + + editorPages.append (KTextEditor::configInterfaceExtension (this)->configPage(i, page)); + } + + if (kd->exec()) + { + KateDocumentConfig::global()->configStart (); + KateViewConfig::global()->configStart (); + KateRendererConfig::global()->configStart (); + + for (uint i=0; i<editorPages.count(); i++) + { + editorPages.at(i)->apply(); + } + + KateDocumentConfig::global()->configEnd (); + KateViewConfig::global()->configEnd (); + KateRendererConfig::global()->configEnd (); + + writeConfig (); + } + + delete kd; +} + +uint KateDocument::mark( uint line ) +{ + if( !m_marks[line] ) + return 0; + return m_marks[line]->type; +} + +void KateDocument::setMark( uint line, uint markType ) +{ + clearMark( line ); + addMark( line, markType ); +} + +void KateDocument::clearMark( uint line ) +{ + if( line > lastLine() ) + return; + + if( !m_marks[line] ) + return; + + KTextEditor::Mark* mark = m_marks.take( line ); + emit markChanged( *mark, MarkRemoved ); + emit marksChanged(); + delete mark; + tagLines( line, line ); + repaintViews(true); +} + +void KateDocument::addMark( uint line, uint markType ) +{ + if( line > lastLine()) + return; + + if( markType == 0 ) + return; + + if( m_marks[line] ) { + KTextEditor::Mark* mark = m_marks[line]; + + // Remove bits already set + markType &= ~mark->type; + + if( markType == 0 ) + return; + + // Add bits + mark->type |= markType; + } else { + KTextEditor::Mark *mark = new KTextEditor::Mark; + mark->line = line; + mark->type = markType; + m_marks.insert( line, mark ); + } + + // Emit with a mark having only the types added. + KTextEditor::Mark temp; + temp.line = line; + temp.type = markType; + emit markChanged( temp, MarkAdded ); + + emit marksChanged(); + tagLines( line, line ); + repaintViews(true); +} + +void KateDocument::removeMark( uint line, uint markType ) +{ + if( line > lastLine() ) + return; + if( !m_marks[line] ) + return; + + KTextEditor::Mark* mark = m_marks[line]; + + // Remove bits not set + markType &= mark->type; + + if( markType == 0 ) + return; + + // Subtract bits + mark->type &= ~markType; + + // Emit with a mark having only the types removed. + KTextEditor::Mark temp; + temp.line = line; + temp.type = markType; + emit markChanged( temp, MarkRemoved ); + + if( mark->type == 0 ) + m_marks.remove( line ); + + emit marksChanged(); + tagLines( line, line ); + repaintViews(true); +} + +QPtrList<KTextEditor::Mark> KateDocument::marks() +{ + QPtrList<KTextEditor::Mark> list; + + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current(); ++it ) { + list.append( it.current() ); + } + + return list; +} + +void KateDocument::clearMarks() +{ + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); + it.current(); ++it ) { + KTextEditor::Mark* mark = it.current(); + emit markChanged( *mark, MarkRemoved ); + tagLines( mark->line, mark->line ); + } + + m_marks.clear(); + + emit marksChanged(); + repaintViews(true); +} + +void KateDocument::setPixmap( MarkInterface::MarkTypes type, const QPixmap& pixmap ) +{ + m_markPixmaps.replace( type, new QPixmap( pixmap ) ); +} + +void KateDocument::setDescription( MarkInterface::MarkTypes type, const QString& description ) +{ + m_markDescriptions.replace( type, new QString( description ) ); +} + +QPixmap *KateDocument::markPixmap( MarkInterface::MarkTypes type ) +{ + return m_markPixmaps[type]; +} + +QColor KateDocument::markColor( MarkInterface::MarkTypes type ) +{ + uint reserved = (0x1 << KTextEditor::MarkInterface::reservedMarkersCount()) - 1; + if ((uint)type >= (uint)markType01 && (uint)type <= reserved) { + return KateRendererConfig::global()->lineMarkerColor(type); + } else { + return QColor(); + } +} + +QString KateDocument::markDescription( MarkInterface::MarkTypes type ) +{ + if( m_markDescriptions[type] ) + return *m_markDescriptions[type]; + return QString::null; +} + +void KateDocument::setMarksUserChangable( uint markMask ) +{ + m_editableMarks = markMask; +} + +uint KateDocument::editableMarks() +{ + return m_editableMarks; +} +//END + +//BEGIN KTextEditor::PrintInterface stuff +bool KateDocument::printDialog () +{ + return KatePrinter::print (this); +} + +bool KateDocument::print () +{ + return KatePrinter::print (this); +} +//END + +//BEGIN KTextEditor::DocumentInfoInterface (### unfinished) +QString KateDocument::mimeType() +{ + KMimeType::Ptr result = KMimeType::defaultMimeTypePtr(); + + // if the document has a URL, try KMimeType::findByURL + if ( ! m_url.isEmpty() ) + result = KMimeType::findByURL( m_url ); + + else if ( m_url.isEmpty() || ! m_url.isLocalFile() ) + result = mimeTypeForContent(); + + return result->name(); +} + +// TODO implement this -- how to calculate? +long KateDocument::fileSize() +{ + return 0; +} + +// TODO implement this +QString KateDocument::niceFileSize() +{ + return "UNKNOWN"; +} + +KMimeType::Ptr KateDocument::mimeTypeForContent() +{ + QByteArray buf (1024); + uint bufpos = 0; + + for (uint i=0; i < numLines(); i++) + { + QString line = textLine( i ); + uint len = line.length() + 1; + + if (bufpos + len > 1024) + len = 1024 - bufpos; + + memcpy(&buf[bufpos], (line + "\n").latin1(), len); + + bufpos += len; + + if (bufpos >= 1024) + break; + } + buf.resize( bufpos ); + + int accuracy = 0; + return KMimeType::findByContent( buf, &accuracy ); +} +//END KTextEditor::DocumentInfoInterface + + +//BEGIN KParts::ReadWrite stuff + +bool KateDocument::openURL( const KURL &url ) +{ +// kdDebug(13020)<<"KateDocument::openURL( "<<url.prettyURL()<<")"<<endl; + // no valid URL + if ( !url.isValid() ) + return false; + + // could not close old one + if ( !closeURL() ) + return false; + + // set my url + m_url = url; + + if ( m_url.isLocalFile() ) + { + // local mode, just like in kpart + + m_file = m_url.path(); + + emit started( 0 ); + + if (openFile()) + { + emit completed(); + emit setWindowCaption( m_url.prettyURL() ); + + return true; + } + + return false; + } + else + { + // remote mode + + m_bTemp = true; + + m_tempFile = new KTempFile (); + m_file = m_tempFile->name(); + + m_job = KIO::get ( url, false, isProgressInfoEnabled() ); + + // connect to slots + connect( m_job, SIGNAL( data( KIO::Job*, const QByteArray& ) ), + SLOT( slotDataKate( KIO::Job*, const QByteArray& ) ) ); + + connect( m_job, SIGNAL( result( KIO::Job* ) ), + SLOT( slotFinishedKate( KIO::Job* ) ) ); + + QWidget *w = widget (); + if (!w && !m_views.isEmpty ()) + w = m_views.first(); + + if (w) + m_job->setWindow (w->topLevelWidget()); + + emit started( m_job ); + + return true; + } +} + +void KateDocument::slotDataKate ( KIO::Job *, const QByteArray &data ) +{ +// kdDebug(13020) << "KateDocument::slotData" << endl; + + if (!m_tempFile || !m_tempFile->file()) + return; + + m_tempFile->file()->writeBlock (data); +} + +void KateDocument::slotFinishedKate ( KIO::Job * job ) +{ +// kdDebug(13020) << "KateDocument::slotJobFinished" << endl; + + if (!m_tempFile) + return; + + delete m_tempFile; + m_tempFile = 0; + m_job = 0; + + if (job->error()) + emit canceled( job->errorString() ); + else + { + if ( openFile(job) ) + emit setWindowCaption( m_url.prettyURL() ); + emit completed(); + } +} + +void KateDocument::abortLoadKate() +{ + if ( m_job ) + { + kdDebug(13020) << "Aborting job " << m_job << endl; + m_job->kill(); + m_job = 0; + } + + delete m_tempFile; + m_tempFile = 0; +} + +bool KateDocument::openFile() +{ + return openFile (0); +} + +bool KateDocument::openFile(KIO::Job * job) +{ + m_loading = true; + // add new m_file to dirwatch + activateDirWatch (); + + // + // use metadata + // + if (job) + { + QString metaDataCharset = job->queryMetaData("charset"); + + // only overwrite config if nothing set + if (!metaDataCharset.isEmpty () && (!m_config->isSetEncoding() || m_config->encoding().isEmpty())) + setEncoding (metaDataCharset); + } + + // + // service type magic to get encoding right + // + QString serviceType = m_extension->urlArgs().serviceType.simplifyWhiteSpace(); + int pos = serviceType.find(';'); + if (pos != -1) + setEncoding (serviceType.mid(pos+1)); + + // if the encoding is set here - on the command line/from the dialog/from KIO + // we prevent file type and document variables from changing it + bool encodingSticky = m_encodingSticky; + m_encodingSticky = m_config->isSetEncoding(); + + // Try getting the filetype here, so that variables does not have to be reset. + int fileTypeFound = KateFactory::self()->fileTypeManager()->fileType (this); + if ( fileTypeFound > -1 ) + updateFileType( fileTypeFound ); + + // do we have success ? + bool success = m_buffer->openFile (m_file); + // + // yeah, success + // + m_loading = false; // done reading file. + if (success) + { + /*if (highlight() && !m_url.isLocalFile()) { + // The buffer's highlighting gets nuked by KateBuffer::clear() + m_buffer->setHighlight(m_highlight); + }*/ + + // update our hl type if needed + if (!hlSetByUser) + { + int hl (KateHlManager::self()->detectHighlighting (this)); + + if (hl >= 0) + m_buffer->setHighlight(hl); + } + + // update file type if we haven't allready done so. + if ( fileTypeFound < 0 ) + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + + // read dir config (if possible and wanted) + readDirConfig (); + + // read vars + readVariables(); + + // update the md5 digest + createDigest( m_digest ); + } + + // + // update views + // + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->updateView(true); + } + + // + // emit the signal we need for example for kate app + // + emit fileNameChanged (); + + // + // set doc name, dummy value as arg, don't need it + // + setDocName (QString::null); + + // + // to houston, we are not modified + // + if (m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // + // display errors + // + if (s_openErrorDialogsActivated) + { + if (!success && m_buffer->loadingBorked()) + KMessageBox::error (widget(), i18n ("The file %1 could not be loaded completely, as there is not enough temporary disk storage for it.").arg(m_url.url())); + else if (!success) + KMessageBox::error (widget(), 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.").arg(m_url.url())); + } + + // warn -> opened binary file!!!!!!! + if (m_buffer->binary()) + { + // this file can't be saved again without killing it + setReadWrite( false ); + + KMessageBox::information (widget() + , i18n ("The file %1 is a binary, saving it will result in a corrupt file.").arg(m_url.url()) + , i18n ("Binary File Opened") + , "Binary File Opened Warning"); + } + + m_encodingSticky = encodingSticky; + + // + // return the success + // + return success; +} + +bool KateDocument::save() +{ + bool l ( url().isLocalFile() ); + + if ( ( l && config()->backupFlags() & KateDocumentConfig::LocalFiles ) + || ( ! l && config()->backupFlags() & KateDocumentConfig::RemoteFiles ) ) + { + KURL u( url() ); + u.setFileName( config()->backupPrefix() + url().fileName() + config()->backupSuffix() ); + + kdDebug () << "backup src file name: " << url() << endl; + kdDebug () << "backup dst file name: " << u << endl; + + // get the right permissions, start with safe default + mode_t perms = 0600; + KIO::UDSEntry fentry; + if (KIO::NetAccess::stat (url(), fentry, kapp->mainWidget())) + { + kdDebug () << "stating succesfull: " << url() << endl; + KFileItem item (fentry, url()); + perms = item.permissions(); + } + + // first del existing file if any, than copy over the file we have + // failure if a: the existing file could not be deleted, b: the file could not be copied + if ( (!KIO::NetAccess::exists( u, false, kapp->mainWidget() ) || KIO::NetAccess::del( u, kapp->mainWidget() )) + && KIO::NetAccess::file_copy( url(), u, perms, true, false, kapp->mainWidget() ) ) + { + kdDebug(13020)<<"backing up successfull ("<<url().prettyURL()<<" -> "<<u.prettyURL()<<")"<<endl; + } + else + { + kdDebug(13020)<<"backing up failed ("<<url().prettyURL()<<" -> "<<u.prettyURL()<<")"<<endl; + // FIXME: notify user for real ;) + } + } + + return KParts::ReadWritePart::save(); +} + +bool KateDocument::saveFile() +{ + // + // we really want to save this file ? + // + if (m_buffer->loadingBorked() && (KMessageBox::warningContinueCancel(widget(), + i18n("This file could not be loaded correctly due to lack of temporary disk space. Saving it could cause data loss.\n\nDo you really want to save it?"),i18n("Possible Data Loss"),i18n("Save Nevertheless")) != KMessageBox::Continue)) + return false; + + // + // warn -> try to save binary file!!!!!!! + // + if (m_buffer->binary() && (KMessageBox::warningContinueCancel (widget() + , i18n ("The file %1 is a binary, saving it will result in a corrupt file.").arg(m_url.url()) + , i18n ("Trying to Save Binary File") + , i18n("Save Nevertheless"), "Binary File Save Warning") != KMessageBox::Continue)) + return false; + + if ( !url().isEmpty() ) + { + if (s_fileChangedDialogsActivated && m_modOnHd) + { + QString str = reasonedMOHString() + "\n\n"; + + if (!isModified()) + { + if (KMessageBox::warningContinueCancel(0, + 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"),i18n("Save Nevertheless")) != KMessageBox::Continue) + return false; + } + else + { + if (KMessageBox::warningContinueCancel(0, + 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"),i18n("Save Nevertheless")) != KMessageBox::Continue) + return false; + } + } + } + + // + // can we encode it if we want to save it ? + // + if (!m_buffer->canEncode () + && (KMessageBox::warningContinueCancel(0, + 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"),i18n("Save Nevertheless")) != KMessageBox::Continue)) + { + return false; + } + + // remove file from dirwatch + deactivateDirWatch (); + + // + // try to save + // + bool success = m_buffer->saveFile (m_file); + + // update the md5 digest + createDigest( m_digest ); + + // add m_file again to dirwatch + activateDirWatch (); + + // + // hurray, we had success, do stuff we need + // + if (success) + { + // update our hl type if needed + if (!hlSetByUser) + { + int hl (KateHlManager::self()->detectHighlighting (this)); + + if (hl >= 0) + m_buffer->setHighlight(hl); + } + + // read our vars + readVariables(); + } + + // + // we are not modified + // + if (success && m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // + // display errors + // + if (!success) + KMessageBox::error (widget(), 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.").arg(m_url.url())); + + // + // return success + // + return success; +} + +bool KateDocument::saveAs( const KURL &u ) +{ + QString oldDir = url().directory(); + + if ( KParts::ReadWritePart::saveAs( u ) ) + { + // null means base on filename + setDocName( QString::null ); + + if ( u.directory() != oldDir ) + readDirConfig(); + + emit fileNameChanged(); + emit nameChanged((Kate::Document *) this); + + return true; + } + + return false; +} + +void KateDocument::readDirConfig () +{ + int depth = config()->searchDirConfigDepth (); + + if (m_url.isLocalFile() && (depth > -1)) + { + QString currentDir = QFileInfo (m_file).dirPath(); + + // only search as deep as specified or not at all ;) + while (depth > -1) + { + kdDebug (13020) << "search for config file in path: " << currentDir << endl; + + // try to open config file in this dir + QFile f (currentDir + "/.kateconfig"); + + if (f.open (IO_ReadOnly)) + { + QTextStream stream (&f); + + uint linesRead = 0; + QString line = stream.readLine(); + while ((linesRead < 32) && !line.isNull()) + { + readVariableLine( line ); + + line = stream.readLine(); + + linesRead++; + } + + break; + } + + QString newDir = QFileInfo (currentDir).dirPath(); + + // bail out on looping (for example reached /) + if (currentDir == newDir) + break; + + currentDir = newDir; + --depth; + } + } +} + +void KateDocument::activateDirWatch () +{ + // same file as we are monitoring, return + if (m_file == m_dirWatchFile) + return; + + // remove the old watched file + deactivateDirWatch (); + + // add new file if needed + if (m_url.isLocalFile() && !m_file.isEmpty()) + { + KateFactory::self()->dirWatch ()->addFile (m_file); + m_dirWatchFile = m_file; + } +} + +void KateDocument::deactivateDirWatch () +{ + if (!m_dirWatchFile.isEmpty()) + KateFactory::self()->dirWatch ()->removeFile (m_dirWatchFile); + + m_dirWatchFile = QString::null; +} + +bool KateDocument::closeURL() +{ + abortLoadKate(); + + // + // file mod on hd + // + if ( !m_reloading && !url().isEmpty() ) + { + if (s_fileChangedDialogsActivated && m_modOnHd) + { + if (!(KMessageBox::warningContinueCancel( + widget(), + reasonedMOHString() + "\n\n" + i18n("Do you really want to continue to close this file? Data loss may occur."), + i18n("Possible Data Loss"), i18n("Close Nevertheless"), + QString("kate_close_modonhd_%1").arg( m_modOnHdReason ) ) == KMessageBox::Continue)) + return false; + } + } + + // + // first call the normal kparts implementation + // + if (!KParts::ReadWritePart::closeURL ()) + return false; + + // remove file from dirwatch + deactivateDirWatch (); + + // + // empty url + filename + // + m_url = KURL (); + m_file = QString::null; + + // we are not modified + if (m_modOnHd) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + // clear the buffer + m_buffer->clear(); + + // remove all marks + clearMarks (); + + // clear undo/redo history + clearUndo(); + clearRedo(); + + // no, we are no longer modified + setModified(false); + + // we have no longer any hl + m_buffer->setHighlight(0); + + // update all our views + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + // Explicitly call the internal version because we don't want this to look like + // an external request (and thus have the view not QWidget::scroll()ed. + view->setCursorPositionInternal(0, 0, 1, false); + view->clearSelection(); + view->updateView(true); + } + + // uh, filename changed + emit fileNameChanged (); + + // update doc name + setDocName (QString::null); + + // success + return true; +} + +void KateDocument::setReadWrite( bool rw ) +{ + if (isReadWrite() != rw) + { + KParts::ReadWritePart::setReadWrite (rw); + + for( KateView* view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->slotUpdate(); + view->slotReadWriteChanged (); + } + } +} + +void KateDocument::setModified(bool m) { + + if (isModified() != m) { + KParts::ReadWritePart::setModified (m); + + for( KateView* view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->slotUpdate(); + } + + emit modifiedChanged (); + emit modStateChanged ((Kate::Document *)this); + } + if ( m == false ) + { + if ( ! undoItems.isEmpty() ) + { + lastUndoGroupWhenSaved = undoItems.last(); + } + + if ( ! redoItems.isEmpty() ) + { + lastRedoGroupWhenSaved = redoItems.last(); + } + + docWasSavedWhenUndoWasEmpty = undoItems.isEmpty(); + docWasSavedWhenRedoWasEmpty = redoItems.isEmpty(); + } +} +//END + +//BEGIN Kate specific stuff ;) + +void KateDocument::makeAttribs(bool needInvalidate) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->renderer()->updateAttributes (); + + if (needInvalidate) + m_buffer->invalidateHighlighting(); + + tagAll (); +} + +// the attributes of a hl have changed, update +void KateDocument::internalHlChanged() +{ + makeAttribs(); +} + +void KateDocument::addView(KTextEditor::View *view) { + if (!view) + return; + + m_views.append( (KateView *) view ); + m_textEditViews.append( view ); + + // apply the view & renderer vars from the file type + const KateFileType *t = 0; + if ((m_fileType > -1) && (t = KateFactory::self()->fileTypeManager()->fileType(m_fileType))) + readVariableLine (t->varLine, true); + + // apply the view & renderer vars from the file + readVariables (true); + + m_activeView = (KateView *) view; +} + +void KateDocument::removeView(KTextEditor::View *view) { + if (!view) + return; + + if (m_activeView == view) + m_activeView = 0L; + + m_views.removeRef( (KateView *) view ); + m_textEditViews.removeRef( view ); +} + +void KateDocument::addSuperCursor(KateSuperCursor *cursor, bool privateC) { + if (!cursor) + return; + + m_superCursors.append( cursor ); + + if (!privateC) + myCursors.append( cursor ); +} + +void KateDocument::removeSuperCursor(KateSuperCursor *cursor, bool privateC) { + if (!cursor) + return; + + if (!privateC) + myCursors.removeRef( cursor ); + + m_superCursors.removeRef( cursor ); +} + +bool KateDocument::ownedView(KateView *view) { + // do we own the given view? + return (m_views.containsRef(view) > 0); +} + +bool KateDocument::isLastView(int numViews) { + return ((int) m_views.count() == numViews); +} + +uint KateDocument::currentColumn( const KateTextCursor& cursor ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + + if (textLine) + return textLine->cursorX(cursor.col(), config()->tabWidth()); + else + return 0; +} + +bool KateDocument::typeChars ( KateView *view, const QString &chars ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(view->cursorLine ()); + + if (!textLine) + return false; + + bool bracketInserted = false; + QString buf; + QChar c; + + for( uint z = 0; z < chars.length(); z++ ) + { + QChar ch = c = chars[z]; + if (ch.isPrint() || ch == '\t') + { + buf.append (ch); + + if (!bracketInserted && (config()->configFlags() & KateDocument::cfAutoBrackets)) + { + QChar end_ch; + bool complete = true; + QChar prevChar = textLine->getChar(view->cursorColumnReal()-1); + QChar nextChar = textLine->getChar(view->cursorColumnReal()); + switch(ch) { + case '(': end_ch = ')'; break; + case '[': end_ch = ']'; break; + case '{': end_ch = '}'; break; + case '\'':end_ch = '\'';break; + case '"': end_ch = '"'; break; + default: complete = false; + } + if (complete) + { + if (view->hasSelection()) + { // there is a selection, enclose the selection + buf.append (view->selection()); + buf.append (end_ch); + bracketInserted = true; + } + else + { // no selection, check whether we should better refuse to complete + if ( ( (ch == '\'' || ch == '"') && + (prevChar.isLetterOrNumber() || prevChar == ch) ) + || nextChar.isLetterOrNumber() + || (nextChar == end_ch && prevChar != ch) ) + { + kdDebug(13020) << "AutoBracket refused before: " << nextChar << "\n"; + } + else + { + buf.append (end_ch); + bracketInserted = true; + } + } + } + } + } + } + + if (buf.isEmpty()) + return false; + + editStart (); + + if (!view->config()->persistentSelection() && view->hasSelection() ) + view->removeSelectedText(); + + int oldLine = view->cursorLine (); + int oldCol = view->cursorColumnReal (); + + + if (config()->configFlags() & KateDocument::cfOvr) + removeText (view->cursorLine(), view->cursorColumnReal(), view->cursorLine(), kMin( view->cursorColumnReal()+buf.length(), textLine->length() ) ); + + insertText (view->cursorLine(), view->cursorColumnReal(), buf); + m_indenter->processChar(c); + + editEnd (); + + if (bracketInserted) + view->setCursorPositionInternal (view->cursorLine(), view->cursorColumnReal()-1); + + emit charactersInteractivelyInserted (oldLine, oldCol, chars); + + return true; +} + +void KateDocument::newLine( KateTextCursor& c, KateViewInternal *v ) +{ + editStart(); + + if( !v->view()->config()->persistentSelection() && v->view()->hasSelection() ) + v->view()->removeSelectedText(); + + // temporary hack to get the cursor pos right !!!!!!!!! + c = v->getCursor (); + + if (c.line() > (int)lastLine()) + c.setLine(lastLine()); + + if ( c.line() < 0 ) + c.setLine( 0 ); + + uint ln = c.line(); + + KateTextLine::Ptr textLine = kateTextLine(c.line()); + + if (c.col() > (int)textLine->length()) + c.setCol(textLine->length()); + + if (m_indenter->canProcessNewLine ()) + { + int pos = textLine->firstChar(); + + // length should do the job better + if (pos < 0) + pos = textLine->length(); + + if (c.col() < pos) + c.setCol(pos); // place cursor on first char if before + + editWrapLine (c.line(), c.col()); + + KateDocCursor cursor (c.line() + 1, pos, this); + m_indenter->processNewline(cursor, true); + + c.setPos(cursor); + } + else + { + editWrapLine (c.line(), c.col()); + c.setPos(c.line() + 1, 0); + } + + removeTrailingSpace( ln ); + + editEnd(); +} + +void KateDocument::transpose( const KateTextCursor& cursor) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + + if (!textLine || (textLine->length() < 2)) + return; + + uint col = cursor.col(); + + if (col > 0) + col--; + + if ((textLine->length() - col) < 2) + return; + + uint line = cursor.line(); + QString s; + + //clever swap code if first character on the line swap right&left + //otherwise left & right + s.append (textLine->getChar(col+1)); + s.append (textLine->getChar(col)); + //do the swap + + // do it right, never ever manipulate a textline + editStart (); + editRemoveText (line, col, 2); + editInsertText (line, col, s); + editEnd (); +} + +void KateDocument::backspace( KateView *view, const KateTextCursor& c ) +{ + if ( !view->config()->persistentSelection() && view->hasSelection() ) { + view->removeSelectedText(); + return; + } + + uint col = kMax( c.col(), 0 ); + uint line = kMax( c.line(), 0 ); + + if ((col == 0) && (line == 0)) + return; + + int complement = 0; + if (col > 0) + { + if (config()->configFlags() & KateDocument::cfAutoBrackets) + { + // if inside empty (), {}, [], '', "" delete both + KateTextLine::Ptr tl = m_buffer->plainLine(line); + if(!tl) return; + QChar prevChar = tl->getChar(col-1); + QChar nextChar = tl->getChar(col); + + if ( (prevChar == '"' && nextChar == '"') || + (prevChar == '\'' && nextChar == '\'') || + (prevChar == '(' && nextChar == ')') || + (prevChar == '[' && nextChar == ']') || + (prevChar == '{' && nextChar == '}') ) + { + complement = 1; + } + } + if (!(config()->configFlags() & KateDocument::cfBackspaceIndents)) + { + // ordinary backspace + //c.cursor.col--; + removeText(line, col-1, line, col+complement); + } + else + { + // backspace indents: erase to next indent position + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + // don't forget this check!!!! really!!!! + if (!textLine) + return; + + int colX = textLine->cursorX(col, config()->tabWidth()); + int pos = textLine->firstChar(); + if (pos > 0) + pos = textLine->cursorX(pos, config()->tabWidth()); + + if (pos < 0 || pos >= (int)colX) + { + // only spaces on left side of cursor + indent( view, line, -1); + } + else + removeText(line, col-1, line, col+complement); + } + } + else + { + // col == 0: wrap to previous line + if (line >= 1) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line-1); + + // don't forget this check!!!! really!!!! + if (!textLine) + return; + + if (config()->wordWrap() && textLine->endingWith(QString::fromLatin1(" "))) + { + // gg: in hard wordwrap mode, backspace must also eat the trailing space + removeText (line-1, textLine->length()-1, line, 0); + } + else + removeText (line-1, textLine->length(), line, 0); + } + } + + emit backspacePressed(); +} + +void KateDocument::del( KateView *view, const KateTextCursor& c ) +{ + if ( !view->config()->persistentSelection() && view->hasSelection() ) { + view->removeSelectedText(); + return; + } + + if( c.col() < (int) m_buffer->plainLine(c.line())->length()) + { + removeText(c.line(), c.col(), c.line(), c.col()+1); + } + else if ( (uint)c.line() < lastLine() ) + { + removeText(c.line(), c.col(), c.line()+1, 0); + } +} + +void KateDocument::paste ( KateView* view ) +{ + QString s = QApplication::clipboard()->text(); + + if (s.isEmpty()) + return; + + uint lines = s.contains (QChar ('\n')); + + m_undoDontMerge = true; + + editStart (); + + if (!view->config()->persistentSelection() && view->hasSelection() ) + view->removeSelectedText(); + + uint line = view->cursorLine (); + uint column = view->cursorColumnReal (); + + insertText ( line, column, s, view->blockSelectionMode() ); + + editEnd(); + + // move cursor right for block select, as the user is moved right internal + // even in that case, but user expects other behavior in block selection + // mode ! + if (view->blockSelectionMode()) + view->setCursorPositionInternal (line+lines, column); + + if (m_indenter->canProcessLine() + && config()->configFlags() & KateDocumentConfig::cfIndentPastedText) + { + editStart(); + + KateDocCursor begin(line, 0, this); + KateDocCursor end(line + lines, 0, this); + + m_indenter->processSection (begin, end); + + editEnd(); + } + + if (!view->blockSelectionMode()) emit charactersSemiInteractivelyInserted (line, column, s); + m_undoDontMerge = true; +} + +void KateDocument::insertIndentChars ( KateView *view ) +{ + editStart (); + + QString s; + if (config()->configFlags() & KateDocument::cfSpaceIndent) + { + int width = config()->indentationWidth(); + s.fill (' ', width - (view->cursorColumnReal() % width)); + } + else + s.append ('\t'); + + insertText (view->cursorLine(), view->cursorColumnReal(), s); + + editEnd (); +} + +void KateDocument::indent ( KateView *v, uint line, int change) +{ + editStart (); + + if (!hasSelection()) + { + // single line + optimizeLeadingSpace(line, config()->configFlags(), change); + } + else + { + int sl = v->selStartLine(); + int el = v->selEndLine(); + int ec = v->selEndCol(); + + if ((ec == 0) && ((el-1) >= 0)) + { + el--; /* */ + } + + if (config()->configFlags() & KateDocument::cfKeepIndentProfile && change < 0) { + // unindent so that the existing indent profile doesn't get screwed + // if any line we may unindent is already full left, don't do anything + int adjustedChange = -change; + + for (line = sl; (int) line <= el && adjustedChange > 0; line++) { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + int firstChar = textLine->firstChar(); + if (firstChar >= 0 && (v->lineSelected(line) || v->lineHasSelected(line))) { + int maxUnindent = textLine->cursorX(firstChar, config()->tabWidth()) / config()->indentationWidth(); + if (maxUnindent < adjustedChange) + adjustedChange = maxUnindent; + } + } + + change = -adjustedChange; + } + + const bool rts = config()->configFlags() & KateDocumentConfig::cfRemoveTrailingDyn; + for (line = sl; (int) line <= el; line++) { + if ((v->lineSelected(line) || v->lineHasSelected(line)) + && (!rts || lineLength(line) > 0)) { + optimizeLeadingSpace(line, config()->configFlags(), change); + } + } + } + + editEnd (); +} + +void KateDocument::align(KateView *view, uint line) +{ + if (m_indenter->canProcessLine()) + { + editStart (); + + if (!view->hasSelection()) + { + KateDocCursor curLine(line, 0, this); + m_indenter->processLine (curLine); + editEnd (); + activeView()->setCursorPosition (line, curLine.col()); + } + else + { + m_indenter->processSection (view->selStart(), view->selEnd()); + editEnd (); + } + } +} + +/* + Optimize the leading whitespace for a single line. + If change is > 0, it adds indentation units (indentationChars) + if change is == 0, it only optimizes + If change is < 0, it removes indentation units + This will be used to indent, unindent, and optimal-fill a line. + If excess space is removed depends on the flag cfKeepExtraSpaces + which has to be set by the user +*/ +void KateDocument::optimizeLeadingSpace(uint line, int flags, int change) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int first_char = textline->firstChar(); + + int w = 0; + if (flags & KateDocument::cfSpaceIndent) + w = config()->indentationWidth(); + else + w = config()->tabWidth(); + + if (first_char < 0) + first_char = textline->length(); + + int space = textline->cursorX(first_char, config()->tabWidth()) + change * w; + if (space < 0) + space = 0; + + if (!(flags & KateDocument::cfKeepExtraSpaces)) + { + uint extra = space % w; + + space -= extra; + if (extra && change < 0) { + // otherwise it unindents too much (e.g. 12 chars when indentation is 8 chars wide) + space += w; + } + } + + //kdDebug(13020) << "replace With Op: " << line << " " << first_char << " " << space << endl; + replaceWithOptimizedSpace(line, first_char, space, flags); +} + +void KateDocument::replaceWithOptimizedSpace(uint line, uint upto_column, uint space, int flags) +{ + uint length; + QString new_space; + + if (flags & KateDocument::cfSpaceIndent && ! (flags & KateDocumentConfig::cfMixedIndent) ) { + length = space; + new_space.fill(' ', length); + } + else { + length = space / config()->tabWidth(); + new_space.fill('\t', length); + + QString extra_space; + extra_space.fill(' ', space % config()->tabWidth()); + length += space % config()->tabWidth(); + new_space += extra_space; + } + + KateTextLine::Ptr textline = m_buffer->plainLine(line); + uint change_from; + for (change_from = 0; change_from < upto_column && change_from < length; change_from++) { + if (textline->getChar(change_from) != new_space[change_from]) + break; + } + + editStart(); + + if (change_from < upto_column) + removeText(line, change_from, line, upto_column); + + if (change_from < length) + insertText(line, change_from, new_space.right(length - change_from)); + + editEnd(); +} + +/* + Remove a given string at the begining + of the current line. +*/ +bool KateDocument::removeStringFromBegining(int line, QString &str) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int index = 0; + bool there = false; + + if (textline->startingWith(str)) + there = true; + else + { + index = textline->firstChar (); + + if ((index >= 0) && (textline->length() >= (index + str.length())) && (textline->string(index, str.length()) == str)) + there = true; + } + + if (there) + { + // Remove some chars + removeText (line, index, line, index+str.length()); + } + + return there; +} + +/* + Remove a given string at the end + of the current line. +*/ +bool KateDocument::removeStringFromEnd(int line, QString &str) +{ + KateTextLine::Ptr textline = m_buffer->plainLine(line); + + int index = 0; + bool there = false; + + if(textline->endingWith(str)) + { + index = textline->length() - str.length(); + there = true; + } + else + { + index = textline->lastChar ()-str.length()+1; + + if ((index >= 0) && (textline->length() >= (index + str.length())) && (textline->string(index, str.length()) == str)) + there = true; + } + + if (there) + { + // Remove some chars + removeText (line, index, line, index+str.length()); + } + + return there; +} + +/* + Add to the current line a comment line mark at + the begining. +*/ +void KateDocument::addStartLineCommentToSingleLine( int line, int attrib ) +{ + if (highlight()->getCommentSingleLinePosition(attrib)==KateHighlighting::CSLPosColumn0) + { + QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + " "; + insertText (line, 0, commentLineMark); + } + else + { + QString commentLineMark=highlight()->getCommentSingleLineStart(attrib); + KateTextLine::Ptr l = m_buffer->line(line); + int pos=l->firstChar(); + if (pos >=0) + insertText(line,pos,commentLineMark); + } +} + +/* + Remove from the current line a comment line mark at + the begining if there is one. +*/ +bool KateDocument::removeStartLineCommentFromSingleLine( int line, int attrib ) +{ + QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib ); + QString longCommentMark = shortCommentMark + " "; + + editStart(); + + // Try to remove the long comment mark first + bool removed = (removeStringFromBegining(line, longCommentMark) + || removeStringFromBegining(line, shortCommentMark)); + + editEnd(); + + return removed; +} + +/* + Add to the current line a start comment mark at the + begining and a stop comment mark at the end. +*/ +void KateDocument::addStartStopCommentToSingleLine( int line, int attrib ) +{ + QString startCommentMark = highlight()->getCommentStart( attrib ) + " "; + QString stopCommentMark = " " + highlight()->getCommentEnd( attrib ); + + editStart(); + + // Add the start comment mark + insertText (line, 0, startCommentMark); + + // Go to the end of the line + int col = m_buffer->plainLine(line)->length(); + + // Add the stop comment mark + insertText (line, col, stopCommentMark); + + editEnd(); +} + +/* + Remove from the current line a start comment mark at + the begining and a stop comment mark at the end. +*/ +bool KateDocument::removeStartStopCommentFromSingleLine( int line, int attrib ) +{ + QString shortStartCommentMark = highlight()->getCommentStart( attrib ); + QString longStartCommentMark = shortStartCommentMark + " "; + QString shortStopCommentMark = highlight()->getCommentEnd( attrib ); + QString longStopCommentMark = " " + shortStopCommentMark; + + editStart(); + +#ifdef __GNUC__ +#warning "that's a bad idea, can lead to stray endings, FIXME" +#endif + // Try to remove the long start comment mark first + bool removedStart = (removeStringFromBegining(line, longStartCommentMark) + || removeStringFromBegining(line, shortStartCommentMark)); + + bool removedStop = false; + if (removedStart) + { + // Try to remove the long stop comment mark first + removedStop = (removeStringFromEnd(line, longStopCommentMark) + || removeStringFromEnd(line, shortStopCommentMark)); + } + + editEnd(); + + return (removedStart || removedStop); +} + +/* + Add to the current selection a start comment + mark at the begining and a stop comment mark + at the end. +*/ +void KateDocument::addStartStopCommentToSelection( KateView *view, int attrib ) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + + int sl = view->selStartLine(); + int el = view->selEndLine(); + int sc = view->selStartCol(); + int ec = view->selEndCol(); + + if ((ec == 0) && ((el-1) >= 0)) + { + el--; + ec = m_buffer->plainLine (el)->length(); + } + + editStart(); + + insertText (el, ec, endComment); + insertText (sl, sc, startComment); + + editEnd (); + + // Set the new selection + ec += endComment.length() + ( (el == sl) ? startComment.length() : 0 ); + view->setSelection(sl, sc, el, ec); +} + +/* + Add to the current selection a comment line + mark at the begining of each line. +*/ +void KateDocument::addStartLineCommentToSelection( KateView *view, int attrib ) +{ + QString commentLineMark = highlight()->getCommentSingleLineStart( attrib ) + " "; + + int sl = view->selStartLine(); + int el = view->selEndLine(); + + if ((view->selEndCol() == 0) && ((el-1) >= 0)) + { + el--; + } + + editStart(); + + // For each line of the selection + for (int z = el; z >= sl; z--) { + //insertText (z, 0, commentLineMark); + addStartLineCommentToSingleLine(z, attrib ); + } + + editEnd (); + + // Set the new selection + + KateDocCursor end (view->selEnd()); + end.setCol(view->selEndCol() + ((el == view->selEndLine()) ? commentLineMark.length() : 0) ); + + view->setSelection(view->selStartLine(), 0, end.line(), end.col()); +} + +bool KateDocument::nextNonSpaceCharPos(int &line, int &col) +{ + for(; line < (int)m_buffer->count(); line++) { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + break; + + col = textLine->nextNonSpaceChar(col); + if(col != -1) + return true; // Next non-space char found + col = 0; + } + // No non-space char found + line = -1; + col = -1; + return false; +} + +bool KateDocument::previousNonSpaceCharPos(int &line, int &col) +{ + while(true) + { + KateTextLine::Ptr textLine = m_buffer->plainLine(line); + + if (!textLine) + break; + + col = textLine->previousNonSpaceChar(col); + if(col != -1) return true; + if(line == 0) return false; + --line; + col = textLine->length(); +} + // No non-space char found + line = -1; + col = -1; + return false; +} + +/* + Remove from the selection a start comment mark at + the begining and a stop comment mark at the end. +*/ +bool KateDocument::removeStartStopCommentFromSelection( KateView *view, int attrib ) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + + int sl = kMax<int> (0, view->selStartLine()); + int el = kMin<int> (view->selEndLine(), lastLine()); + int sc = view->selStartCol(); + int ec = view->selEndCol(); + + // The selection ends on the char before selectEnd + if (ec != 0) { + ec--; + } else { + if (el > 0) { + el--; + ec = m_buffer->plainLine(el)->length() - 1; + } + } + + int startCommentLen = startComment.length(); + int endCommentLen = endComment.length(); + + // had this been perl or sed: s/^\s*$startComment(.+?)$endComment\s*/$1/ + + bool remove = nextNonSpaceCharPos(sl, sc) + && m_buffer->plainLine(sl)->stringAtPos(sc, startComment) + && previousNonSpaceCharPos(el, ec) + && ( (ec - endCommentLen + 1) >= 0 ) + && m_buffer->plainLine(el)->stringAtPos(ec - endCommentLen + 1, endComment); + + if (remove) { + editStart(); + + removeText (el, ec - endCommentLen + 1, el, ec + 1); + removeText (sl, sc, sl, sc + startCommentLen); + + editEnd (); + // set new selection not necessary, as the selection cursors are KateSuperCursors + } + + return remove; +} + +bool KateDocument::removeStartStopCommentFromRegion(const KateTextCursor &start,const KateTextCursor &end,int attrib) +{ + QString startComment = highlight()->getCommentStart( attrib ); + QString endComment = highlight()->getCommentEnd( attrib ); + int startCommentLen = startComment.length(); + int endCommentLen = endComment.length(); + + bool remove = m_buffer->plainLine(start.line())->stringAtPos(start.col(), startComment) + && ( (end.col() - endCommentLen ) >= 0 ) + && m_buffer->plainLine(end.line())->stringAtPos(end.col() - endCommentLen , endComment); + if (remove) { + editStart(); + removeText(end.line(),end.col()-endCommentLen,end.line(),end.col()); + removeText(start.line(),start.col(),start.line(),start.col()+startCommentLen); + editEnd(); + } + return remove; +} + +/* + Remove from the begining of each line of the + selection a start comment line mark. +*/ +bool KateDocument::removeStartLineCommentFromSelection( KateView *view, int attrib ) +{ + QString shortCommentMark = highlight()->getCommentSingleLineStart( attrib ); + QString longCommentMark = shortCommentMark + " "; + + int sl = view->selStartLine(); + int el = view->selEndLine(); + + if ((view->selEndCol() == 0) && ((el-1) >= 0)) + { + el--; + } + + // Find out how many char will be removed from the last line + int removeLength = 0; + if (m_buffer->plainLine(el)->startingWith(longCommentMark)) + removeLength = longCommentMark.length(); + else if (m_buffer->plainLine(el)->startingWith(shortCommentMark)) + removeLength = shortCommentMark.length(); + + bool removed = false; + + editStart(); + + // For each line of the selection + for (int z = el; z >= sl; z--) + { + // Try to remove the long comment mark first + removed = (removeStringFromBegining(z, longCommentMark) + || removeStringFromBegining(z, shortCommentMark) + || removed); + } + + editEnd(); + // updating selection already done by the KateSuperCursors + return removed; +} + +/* + Comment or uncomment the selection or the current + line if there is no selection. +*/ +void KateDocument::comment( KateView *v, uint line,uint column, int change) +{ + // We need to check that we can sanely comment the selectino or region. + // It is if the attribute of the first and last character of the range to + // comment belongs to the same language definition. + // for lines with no text, we need the attribute for the lines context. + bool hassel = v->hasSelection(); + int startAttrib, endAttrib; + if ( hassel ) + { + KateTextLine::Ptr ln = kateTextLine( v->selStartLine() ); + int l = v->selStartLine(), c = v->selStartCol(); + startAttrib = nextNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0; + + ln = kateTextLine( v->selEndLine() ); + l = v->selEndLine(), c = v->selEndCol(); + endAttrib = previousNonSpaceCharPos( l, c ) ? kateTextLine( l )->attribute( c ) : 0; + } + else + { + KateTextLine::Ptr ln = kateTextLine( line ); + if ( ln->length() ) + { + startAttrib = ln->attribute( ln->firstChar() ); + endAttrib = ln->attribute( ln->lastChar() ); + } + else + { + int l = line, c = 0; + if ( nextNonSpaceCharPos( l, c ) || previousNonSpaceCharPos( l, c ) ) + startAttrib = endAttrib = kateTextLine( l )->attribute( c ); + else + startAttrib = endAttrib = 0; + } + } + + if ( ! highlight()->canComment( startAttrib, endAttrib ) ) + { + kdDebug(13020)<<"canComment( "<<startAttrib<<", "<<endAttrib<<" ) returned false!"<<endl; + return; + } + + bool hasStartLineCommentMark = !(highlight()->getCommentSingleLineStart( startAttrib ).isEmpty()); + bool hasStartStopCommentMark = ( !(highlight()->getCommentStart( startAttrib ).isEmpty()) + && !(highlight()->getCommentEnd( endAttrib ).isEmpty()) ); + + bool removed = false; + + if (change > 0) // comment + { + if ( !hassel ) + { + if ( hasStartLineCommentMark ) + addStartLineCommentToSingleLine( line, startAttrib ); + else if ( hasStartStopCommentMark ) + addStartStopCommentToSingleLine( line, startAttrib ); + } + else + { + // anders: prefer single line comment to avoid nesting probs + // If the selection starts after first char in the first line + // or ends before the last char of the last line, we may use + // multiline comment markers. + // TODO We should try to detect nesting. + // - if selection ends at col 0, most likely she wanted that + // line ignored + if ( hasStartStopCommentMark && + ( !hasStartLineCommentMark || ( + ( v->selStartCol() > m_buffer->plainLine( v->selStartLine() )->firstChar() ) || + ( v->selEndCol() < ((int)m_buffer->plainLine( v->selEndLine() )->length()) ) + ) ) ) + addStartStopCommentToSelection( v, startAttrib ); + else if ( hasStartLineCommentMark ) + addStartLineCommentToSelection( v, startAttrib ); + } + } + else // uncomment + { + if ( !hassel ) + { + removed = ( hasStartLineCommentMark + && removeStartLineCommentFromSingleLine( line, startAttrib ) ) + || ( hasStartStopCommentMark + && removeStartStopCommentFromSingleLine( line, startAttrib ) ); + if ((!removed) && foldingTree()) { + kdDebug(13020)<<"easy approach for uncommenting did not work, trying harder (folding tree)"<<endl; + int commentRegion=(highlight()->commentRegion(startAttrib)); + if (commentRegion){ + KateCodeFoldingNode *n=foldingTree()->findNodeForPosition(line,column); + if (n) { + KateTextCursor start,end; + if ((n->nodeType()==commentRegion) && n->getBegin(foldingTree(), &start) && n->getEnd(foldingTree(), &end)) { + kdDebug(13020)<<"Enclosing region found:"<<start.col()<<"/"<<start.line()<<"-"<<end.col()<<"/"<<end.line()<<endl; + removeStartStopCommentFromRegion(start,end,startAttrib); + } else { + kdDebug(13020)<<"Enclosing region found, but not valid"<<endl; + kdDebug(13020)<<"Region found: "<<n->nodeType()<<" region needed: "<<commentRegion<<endl; + } + //perhaps nested regions should be hadled here too... + } else kdDebug(13020)<<"No enclosing region found"<<endl; + } else kdDebug(13020)<<"No comment region specified for current hl"<<endl; + } + } + else + { + // anders: this seems like it will work with above changes :) + removed = ( hasStartLineCommentMark + && removeStartLineCommentFromSelection( v, startAttrib ) ) + || ( hasStartStopCommentMark + && removeStartStopCommentFromSelection( v, startAttrib ) ); + } + } +} + +void KateDocument::transform( KateView *v, const KateTextCursor &c, + KateDocument::TextTransform t ) +{ + editStart(); + uint cl( c.line() ), cc( c.col() ); + bool selectionRestored = false; + + if ( hasSelection() ) + { + // cache the selection and cursor, so we can be sure to restore. + KateTextCursor selstart = v->selStart(); + KateTextCursor selend = v->selEnd(); + + int ln = v->selStartLine(); + while ( ln <= selend.line() ) + { + uint start, end; + start = (ln == selstart.line() || v->blockSelectionMode()) ? + selstart.col() : 0; + end = (ln == selend.line() || v->blockSelectionMode()) ? + selend.col() : lineLength( ln ); + if ( start > end ) + { + uint t = start; + start = end; + end = t; + } + QString s = text( ln, start, ln, end ); + QString o = s; + + if ( t == Uppercase ) + s = s.upper(); + else if ( t == Lowercase ) + s = s.lower(); + else // Capitalize + { + KateTextLine::Ptr l = m_buffer->plainLine( ln ); + uint p ( 0 ); + while( p < s.length() ) + { + // If bol or the character before is not in a word, up this one: + // 1. if both start and p is 0, upper char. + // 2. if blockselect or first line, and p == 0 and start-1 is not in a word, upper + // 3. if p-1 is not in a word, upper. + if ( ( ! start && ! p ) || + ( ( ln == selstart.line() || v->blockSelectionMode() ) && + ! p && ! highlight()->isInWord( l->getChar( start - 1 )) ) || + ( p && ! highlight()->isInWord( s.at( p-1 ) ) ) + ) + s[p] = s.at(p).upper(); + p++; + } + } + + if ( o != s ) + { + removeText( ln, start, ln, end ); + insertText( ln, start, s ); + } + + ln++; + } + + // restore selection + v->setSelection( selstart, selend ); + selectionRestored = true; + + } else { // no selection + QString o = text( cl, cc, cl, cc + 1 ); + QString s; + int n ( cc ); + switch ( t ) { + case Uppercase: + s = o.upper(); + break; + case Lowercase: + s = o.lower(); + break; + case Capitalize: + { + KateTextLine::Ptr l = m_buffer->plainLine( cl ); + while ( n > 0 && highlight()->isInWord( l->getChar( n-1 ), l->attribute( n-1 ) ) ) + n--; + o = text( cl, n, cl, n + 1 ); + s = o.upper(); + } + break; + default: + break; + } + + if ( s != o ) + { + removeText( cl, n, cl, n+1 ); + insertText( cl, n, s ); + } + } + editEnd(); + + if ( ! selectionRestored ) + v->setCursorPosition( cl, cc ); +} + +void KateDocument::joinLines( uint first, uint last ) +{ +// if ( first == last ) last += 1; + editStart(); + int line( first ); + while ( first < last ) + { + // Normalize the whitespace in the joined lines by making sure there's + // always exactly one space between the joined lines + // This cannot be done in editUnwrapLine, because we do NOT want this + // behaviour when deleting from the start of a line, just when explicitly + // calling the join command + KateTextLine::Ptr l = m_buffer->line( line ); + KateTextLine::Ptr tl = m_buffer->line( line + 1 ); + + if ( !l || !tl ) + { + editEnd(); + return; + } + + int pos = tl->firstChar(); + if ( pos >= 0 ) + { + if (pos != 0) + editRemoveText( line + 1, 0, pos ); + if ( !( l->length() == 0 || l->getChar( l->length() - 1 ).isSpace() ) ) + editInsertText( line + 1, 0, " " ); + } + else + { + // Just remove the whitespace and let Kate handle the rest + editRemoveText( line + 1, 0, tl->length() ); + } + + editUnWrapLine( line ); + first++; + } + editEnd(); +} + +QString KateDocument::getWord( const KateTextCursor& cursor ) { + int start, end, len; + + KateTextLine::Ptr textLine = m_buffer->plainLine(cursor.line()); + len = textLine->length(); + start = end = cursor.col(); + if (start > len) // Probably because of non-wrapping cursor mode. + return QString(""); + + while (start > 0 && highlight()->isInWord(textLine->getChar(start - 1), textLine->attribute(start - 1))) start--; + while (end < len && highlight()->isInWord(textLine->getChar(end), textLine->attribute(end))) end++; + len = end - start; + return QString(&textLine->text()[start], len); +} + +void KateDocument::tagLines(int start, int end) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->tagLines (start, end, true); +} + +void KateDocument::tagLines(KateTextCursor start, KateTextCursor end) +{ + // May need to switch start/end cols if in block selection mode + if (blockSelectionMode() && start.col() > end.col()) { + int sc = start.col(); + start.setCol(end.col()); + end.setCol(sc); + } + + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->tagLines(start, end, true); +} + +void KateDocument::repaintViews(bool paintOnlyDirty) +{ + for (uint z = 0; z < m_views.count(); z++) + m_views.at(z)->repaintText(paintOnlyDirty); +} + +void KateDocument::tagAll() +{ + for (uint z = 0; z < m_views.count(); z++) + { + m_views.at(z)->tagAll(); + m_views.at(z)->updateView (true); + } +} + +uint KateDocument::configFlags () +{ + return config()->configFlags(); +} + +void KateDocument::setConfigFlags (uint flags) +{ + config()->setConfigFlags(flags); +} + +inline bool isStartBracket( const QChar& c ) { return c == '{' || c == '[' || c == '('; } +inline bool isEndBracket ( const QChar& c ) { return c == '}' || c == ']' || c == ')'; } +inline bool isBracket ( const QChar& c ) { return isStartBracket( c ) || isEndBracket( c ); } + +/* + Bracket matching uses the following algorithm: + If in overwrite mode, match the bracket currently underneath the cursor. + Otherwise, if the character to the right of the cursor is an starting bracket, + match it. Otherwise if the character to the left of the cursor is a + ending bracket, match it. Otherwise, if the the character to the left + of the cursor is an starting bracket, match it. Otherwise, if the character + to the right of the cursor is an ending bracket, match it. Otherwise, don't + match anything. +*/ +void KateDocument::newBracketMark( const KateTextCursor& cursor, KateBracketRange& bm, int maxLines ) +{ + bm.setValid(false); + + bm.start() = cursor; + + if( !findMatchingBracket( bm.start(), bm.end(), maxLines ) ) + return; + + bm.setValid(true); + + const int tw = config()->tabWidth(); + const int indentStart = m_buffer->plainLine(bm.start().line())->indentDepth(tw); + const int indentEnd = m_buffer->plainLine(bm.end().line())->indentDepth(tw); + bm.setIndentMin(kMin(indentStart, indentEnd)); +} + +bool KateDocument::findMatchingBracket( KateTextCursor& start, KateTextCursor& end, int maxLines ) +{ + KateTextLine::Ptr textLine = m_buffer->plainLine( start.line() ); + if( !textLine ) + return false; + + QChar right = textLine->getChar( start.col() ); + QChar left = textLine->getChar( start.col() - 1 ); + QChar bracket; + + if ( config()->configFlags() & cfOvr ) { + if( isBracket( right ) ) { + bracket = right; + } else { + return false; + } + } else if ( isStartBracket( right ) ) { + bracket = right; + } else if ( isEndBracket( left ) ) { + start.setCol(start.col() - 1); + bracket = left; + } else if ( isBracket( left ) ) { + start.setCol(start.col() - 1); + bracket = left; + } else if ( isBracket( right ) ) { + bracket = right; + } else { + return false; + } + + QChar opposite; + + switch( bracket ) { + case '{': opposite = '}'; break; + case '}': opposite = '{'; break; + case '[': opposite = ']'; break; + case ']': opposite = '['; break; + case '(': opposite = ')'; break; + case ')': opposite = '('; break; + default: return false; + } + + bool forward = isStartBracket( bracket ); + int startAttr = textLine->attribute( start.col() ); + uint count = 0; + int lines = 0; + end = start; + + while( true ) { + /* Increment or decrement, check base cases */ + if( forward ) { + end.setCol(end.col() + 1); + if( end.col() >= lineLength( end.line() ) ) { + if( end.line() >= (int)lastLine() ) + return false; + end.setPos(end.line() + 1, 0); + textLine = m_buffer->plainLine( end.line() ); + lines++; + } + } else { + end.setCol(end.col() - 1); + if( end.col() < 0 ) { + if( end.line() <= 0 ) + return false; + end.setLine(end.line() - 1); + end.setCol(lineLength( end.line() ) - 1); + textLine = m_buffer->plainLine( end.line() ); + lines++; + } + } + + if ((maxLines != -1) && (lines > maxLines)) + return false; + + /* Easy way to skip comments */ + if( textLine->attribute( end.col() ) != startAttr ) + continue; + + /* Check for match */ + QChar c = textLine->getChar( end.col() ); + if( c == bracket ) { + count++; + } else if( c == opposite ) { + if( count == 0 ) + return true; + count--; + } + + } +} + +void KateDocument::guiActivateEvent( KParts::GUIActivateEvent *ev ) +{ + KParts::ReadWritePart::guiActivateEvent( ev ); + if ( ev->activated() ) + emit selectionChanged(); +} + +void KateDocument::setDocName (QString name ) +{ + if ( name == m_docName ) + return; + + if ( !name.isEmpty() ) + { + // TODO check for similarly named documents + m_docName = name; + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + emit nameChanged((Kate::Document *) this); + return; + } + + // if the name is set, and starts with FILENAME, it should not be changed! + if ( ! url().isEmpty() && m_docName.startsWith( url().filename() ) ) return; + + int count = -1; + + for (uint z=0; z < KateFactory::self()->documents()->count(); z++) + { + if ( (KateFactory::self()->documents()->at(z) != this) && (KateFactory::self()->documents()->at(z)->url().filename() == url().filename()) ) + if ( KateFactory::self()->documents()->at(z)->m_docNameNumber > count ) + count = KateFactory::self()->documents()->at(z)->m_docNameNumber; + } + + m_docNameNumber = count + 1; + + m_docName = url().filename(); + + if (m_docName.isEmpty()) + m_docName = i18n ("Untitled"); + + if (m_docNameNumber > 0) + m_docName = QString(m_docName + " (%1)").arg(m_docNameNumber+1); + + updateFileType (KateFactory::self()->fileTypeManager()->fileType (this)); + emit nameChanged ((Kate::Document *) this); +} + +void KateDocument::slotModifiedOnDisk( Kate::View * /*v*/ ) +{ + if ( m_isasking < 0 ) + { + m_isasking = 0; + return; + } + + if ( !s_fileChangedDialogsActivated || m_isasking ) + return; + + if (m_modOnHd && !url().isEmpty()) + { + m_isasking = 1; + + KateModOnHdPrompt p( this, m_modOnHdReason, reasonedMOHString(), widget() ); + switch ( p.exec() ) + { + case KateModOnHdPrompt::Save: + { + m_modOnHd = false; + KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveURLAndEncoding(config()->encoding(), + url().url(),QString::null,widget(),i18n("Save File")); + + kdDebug(13020)<<"got "<<res.URLs.count()<<" URLs"<<endl; + if( ! res.URLs.isEmpty() && ! res.URLs.first().isEmpty() && checkOverwrite( res.URLs.first() ) ) + { + setEncoding( res.encoding ); + + if( ! saveAs( res.URLs.first() ) ) + { + KMessageBox::error( widget(), i18n("Save failed") ); + m_modOnHd = true; + } + else + emit modifiedOnDisc( this, false, 0 ); + } + else // the save as dialog was cancelled, we are still modified on disk + { + m_modOnHd = true; + } + + m_isasking = 0; + break; + } + + case KateModOnHdPrompt::Reload: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + reloadFile(); + m_isasking = 0; + break; + + case KateModOnHdPrompt::Ignore: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + m_isasking = 0; + break; + + case KateModOnHdPrompt::Overwrite: + m_modOnHd = false; + emit modifiedOnDisc( this, false, 0 ); + m_isasking = 0; + save(); + break; + + default: // cancel: ignore next focus event + m_isasking = -1; + } + } +} + +void KateDocument::setModifiedOnDisk( int reason ) +{ + m_modOnHdReason = reason; + m_modOnHd = (reason > 0); + emit modifiedOnDisc( this, (reason > 0), reason ); +} + +class KateDocumentTmpMark +{ + public: + QString line; + KTextEditor::Mark mark; +}; + +void KateDocument::reloadFile() +{ + if ( !url().isEmpty() ) + { + if (m_modOnHd && s_fileChangedDialogsActivated) + { + int i = KMessageBox::warningYesNoCancel + (0, reasonedMOHString() + "\n\n" + i18n("What do you want to do?"), + i18n("File Was Changed on Disk"), i18n("&Reload File"), i18n("&Ignore Changes")); + + if ( i != KMessageBox::Yes) + { + if (i == KMessageBox::No) + { + m_modOnHd = false; + m_modOnHdReason = 0; + emit modifiedOnDisc (this, m_modOnHd, 0); + } + + return; + } + } + + QValueList<KateDocumentTmpMark> tmp; + + for( QIntDictIterator<KTextEditor::Mark> it( m_marks ); it.current(); ++it ) + { + KateDocumentTmpMark m; + + m.line = textLine (it.current()->line); + m.mark = *it.current(); + + tmp.append (m); + } + + uint mode = hlMode (); + bool byUser = hlSetByUser; + + m_storedVariables.clear(); + + m_reloading = true; + + QValueList<int> lines, cols; + for ( uint i=0; i < m_views.count(); i++ ) + { + lines.append( m_views.at( i )->cursorLine() ); + cols.append( m_views.at( i )->cursorColumn() ); + } + + KateDocument::openURL( url() ); + + for ( uint i=0; i < m_views.count(); i++ ) + m_views.at( i )->setCursorPositionInternal( lines[ i ], cols[ i ], m_config->tabWidth(), false ); + + m_reloading = false; + + for ( QValueList<int>::size_type z=0; z < tmp.size(); z++ ) + { + if (z < numLines()) + { + if (textLine(tmp[z].mark.line) == tmp[z].line) + setMark (tmp[z].mark.line, tmp[z].mark.type); + } + } + + if (byUser) + setHlMode (mode); + } +} + +void KateDocument::flush () +{ + closeURL (); +} + +void KateDocument::setWordWrap (bool on) +{ + config()->setWordWrap (on); +} + +bool KateDocument::wordWrap () +{ + return config()->wordWrap (); +} + +void KateDocument::setWordWrapAt (uint col) +{ + config()->setWordWrapAt (col); +} + +unsigned int KateDocument::wordWrapAt () +{ + return config()->wordWrapAt (); +} + +void KateDocument::applyWordWrap () +{ + // dummy to make the API happy +} + +void KateDocument::setPageUpDownMovesCursor (bool on) +{ + config()->setPageUpDownMovesCursor (on); +} + +bool KateDocument::pageUpDownMovesCursor () +{ + return config()->pageUpDownMovesCursor (); +} + +void KateDocument::dumpRegionTree() +{ + m_buffer->foldingTree()->debugDump(); +} +//END + +//BEGIN KTextEditor::CursorInterface stuff + +KTextEditor::Cursor *KateDocument::createCursor ( ) +{ + return new KateSuperCursor (this, false, 0, 0, this); +} + +void KateDocument::tagArbitraryLines(KateView* view, KateSuperRange* range) +{ + if (view) + view->tagLines(range->start(), range->end()); + else + tagLines(range->start(), range->end()); +} + +void KateDocument::lineInfo (KateLineInfo *info, unsigned int line) +{ + m_buffer->lineInfo(info,line); +} + +KateCodeFoldingTree *KateDocument::foldingTree () +{ + return m_buffer->foldingTree(); +} + +void KateDocument::setEncoding (const QString &e) +{ + if ( m_encodingSticky ) + return; + + QString ce = m_config->encoding().lower(); + if ( e.lower() == ce ) + return; + + m_config->setEncoding( e ); + if ( ! m_loading ) + reloadFile(); +} + +QString KateDocument::encoding() const +{ + return m_config->encoding(); +} + +void KateDocument::updateConfig () +{ + emit undoChanged (); + tagAll(); + + for (KateView * view = m_views.first(); view != 0L; view = m_views.next() ) + { + view->updateDocumentConfig (); + } + + // switch indenter if needed + if (m_indenter->modeNumber() != m_config->indentationMode()) + { + delete m_indenter; + m_indenter = KateAutoIndent::createIndenter ( this, m_config->indentationMode() ); + } + + m_indenter->updateConfig(); + + m_buffer->setTabWidth (config()->tabWidth()); + + // plugins + for (uint i=0; i<KateFactory::self()->plugins().count(); i++) + { + if (config()->plugin (i)) + loadPlugin (i); + else + unloadPlugin (i); + } +} + +//BEGIN Variable reader +// "local variable" feature by anders, 2003 +/* TODO + add config options (how many lines to read, on/off) + add interface for plugins/apps to set/get variables + add view stuff +*/ +QRegExp KateDocument::kvLine = QRegExp("kate:(.*)"); +QRegExp KateDocument::kvLineWildcard = QRegExp("kate-wildcard\\((.*)\\):(.*)"); +QRegExp KateDocument::kvLineMime = QRegExp("kate-mimetype\\((.*)\\):(.*)"); +QRegExp KateDocument::kvVar = QRegExp("([\\w\\-]+)\\s+([^;]+)"); + +void KateDocument::readVariables(bool onlyViewAndRenderer) +{ + if (!onlyViewAndRenderer) + m_config->configStart(); + + // views! + KateView *v; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configStart(); + v->renderer()->config()->configStart(); + } + // read a number of lines in the top/bottom of the document + for (uint i=0; i < kMin( 9U, numLines() ); ++i ) + { + readVariableLine( textLine( i ), onlyViewAndRenderer ); + } + if ( numLines() > 10 ) + { + for ( uint i = kMax(10U, numLines() - 10); i < numLines(); ++i ) + { + readVariableLine( textLine( i ), onlyViewAndRenderer ); + } + } + + if (!onlyViewAndRenderer) + m_config->configEnd(); + + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configEnd(); + v->renderer()->config()->configEnd(); + } +} + +void KateDocument::readVariableLine( QString t, bool onlyViewAndRenderer ) +{ + // simple check first, no regex + // no kate inside, no vars, simple... + if (t.find("kate") < 0) + return; + + // found vars, if any + QString s; + + if ( kvLine.search( t ) > -1 ) + { + s = kvLine.cap(1); + + kdDebug (13020) << "normal variable line kate: matched: " << s << endl; + } + else if (kvLineWildcard.search( t ) > -1) // regex given + { + QStringList wildcards (QStringList::split(';', kvLineWildcard.cap(1))); + QString nameOfFile = url().fileName(); + + bool found = false; + for (QStringList::size_type i = 0; !found && i < wildcards.size(); ++i) + { + QRegExp wildcard (wildcards[i], true/*Qt::CaseSensitive*/, true/*QRegExp::Wildcard*/); + + found = wildcard.exactMatch (nameOfFile); + } + + // nothing usable found... + if (!found) + return; + + s = kvLineWildcard.cap(2); + + kdDebug (13020) << "guarded variable line kate-wildcard: matched: " << s << endl; + } + else if (kvLineMime.search( t ) > -1) // mime-type given + { + QStringList types (QStringList::split(';', kvLineMime.cap(1))); + + // no matching type found + if (!types.contains (mimeType ())) + return; + + s = kvLineMime.cap(2); + + kdDebug (13020) << "guarded variable line kate-mimetype: matched: " << s << endl; + } + else // nothing found + { + return; + } + + QStringList vvl; // view variable names + vvl << "dynamic-word-wrap" << "dynamic-word-wrap-indicators" + << "line-numbers" << "icon-border" << "folding-markers" + << "bookmark-sorting" << "auto-center-lines" + << "icon-bar-color" + // renderer + << "background-color" << "selection-color" + << "current-line-color" << "bracket-highlight-color" + << "word-wrap-marker-color" + << "font" << "font-size" << "scheme"; + int p( 0 ); + + QString var, val; + while ( (p = kvVar.search( s, p )) > -1 ) + { + p += kvVar.matchedLength(); + var = kvVar.cap( 1 ); + val = kvVar.cap( 2 ).stripWhiteSpace(); + bool state; // store booleans here + int n; // store ints here + + // only apply view & renderer config stuff + if (onlyViewAndRenderer) + { + if ( vvl.contains( var ) ) // FIXME define above + setViewVariable( var, val ); + } + else + { + // BOOL SETTINGS + if ( var == "word-wrap" && checkBoolValue( val, &state ) ) + setWordWrap( state ); // ??? FIXME CHECK + else if ( var == "block-selection" && checkBoolValue( val, &state ) ) + setBlockSelectionMode( state ); + // KateConfig::configFlags + // FIXME should this be optimized to only a few calls? how? + else if ( var == "backspace-indents" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfBackspaceIndents, state ); + else if ( var == "replace-tabs" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfReplaceTabsDyn, state ); + else if ( var == "remove-trailing-space" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfRemoveTrailingDyn, state ); + else if ( var == "wrap-cursor" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfWrapCursor, state ); + else if ( var == "auto-brackets" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfAutoBrackets, state ); + else if ( var == "overwrite-mode" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfOvr, state ); + else if ( var == "keep-indent-profile" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfKeepIndentProfile, state ); + else if ( var == "keep-extra-spaces" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfKeepExtraSpaces, state ); + else if ( var == "tab-indents" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfTabIndents, state ); + else if ( var == "show-tabs" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfShowTabs, state ); + else if ( var == "space-indent" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfSpaceIndent, state ); + else if ( var == "smart-home" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfSmartHome, state ); + else if ( var == "replace-trailing-space-save" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfRemoveSpaces, state ); + else if ( var == "auto-insert-doxygen" && checkBoolValue( val, &state) ) + m_config->setConfigFlags( KateDocumentConfig::cfDoxygenAutoTyping, state); + else if ( var == "mixed-indent" && checkBoolValue( val, &state ) ) + m_config->setConfigFlags( KateDocumentConfig::cfMixedIndent, state ); + + // INTEGER SETTINGS + else if ( var == "tab-width" && checkIntValue( val, &n ) ) + m_config->setTabWidth( n ); + else if ( var == "indent-width" && checkIntValue( val, &n ) ) + m_config->setIndentationWidth( n ); + else if ( var == "indent-mode" ) + { + if ( checkIntValue( val, &n ) ) + m_config->setIndentationMode( n ); + else + m_config->setIndentationMode( KateAutoIndent::modeNumber( val) ); + } + else if ( var == "word-wrap-column" && checkIntValue( val, &n ) && n > 0 ) // uint, but hard word wrap at 0 will be no fun ;) + m_config->setWordWrapAt( n ); + else if ( var == "undo-steps" && checkIntValue( val, &n ) && n >= 0 ) + setUndoSteps( n ); + + // STRING SETTINGS + else if ( var == "eol" || var == "end-of-line" ) + { + QStringList l; + l << "unix" << "dos" << "mac"; + if ( (n = l.findIndex( val.lower() )) != -1 ) + m_config->setEol( n ); + } + else if ( var == "encoding" ) + m_config->setEncoding( val ); + else if ( var == "syntax" || var == "hl" ) + { + for ( uint i=0; i < hlModeCount(); i++ ) + { + if ( hlModeName( i ).lower() == val.lower() ) + { + setHlMode( i ); + break; + } + } + } + + // VIEW SETTINGS + else if ( vvl.contains( var ) ) + setViewVariable( var, val ); + else + { + m_storedVariables.insert( var, val ); + emit variableChanged( var, val ); + } + } + } +} + +void KateDocument::setViewVariable( QString var, QString val ) +{ + KateView *v; + bool state; + int n; + QColor c; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + if ( var == "dynamic-word-wrap" && checkBoolValue( val, &state ) ) + v->config()->setDynWordWrap( state ); + else if ( var == "persistent-selection" && checkBoolValue( val, &state ) ) + v->config()->setPersistentSelection( state ); + //else if ( var = "dynamic-word-wrap-indicators" ) + else if ( var == "line-numbers" && checkBoolValue( val, &state ) ) + v->config()->setLineNumbers( state ); + else if (var == "icon-border" && checkBoolValue( val, &state ) ) + v->config()->setIconBar( state ); + else if (var == "folding-markers" && checkBoolValue( val, &state ) ) + v->config()->setFoldingBar( state ); + else if ( var == "auto-center-lines" && checkIntValue( val, &n ) ) + v->config()->setAutoCenterLines( n ); // FIXME uint, > N ?? + else if ( var == "icon-bar-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setIconBarColor( c ); + // RENDERER + else if ( var == "background-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setBackgroundColor( c ); + else if ( var == "selection-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setSelectionColor( c ); + else if ( var == "current-line-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setHighlightedLineColor( c ); + else if ( var == "bracket-highlight-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setHighlightedBracketColor( c ); + else if ( var == "word-wrap-marker-color" && checkColorValue( val, c ) ) + v->renderer()->config()->setWordWrapMarkerColor( c ); + else if ( var == "font" || ( var == "font-size" && checkIntValue( val, &n ) ) ) + { + QFont _f( *v->renderer()->config()->font( ) ); + + if ( var == "font" ) + { + _f.setFamily( val ); + _f.setFixedPitch( QFont( val ).fixedPitch() ); + } + else + _f.setPointSize( n ); + + v->renderer()->config()->setFont( _f ); + } + else if ( var == "scheme" ) + { + v->renderer()->config()->setSchema( KateFactory::self()->schemaManager()->number( val ) ); + } + } +} + +bool KateDocument::checkBoolValue( QString val, bool *result ) +{ + val = val.stripWhiteSpace().lower(); + QStringList l; + l << "1" << "on" << "true"; + if ( l.contains( val ) ) + { + *result = true; + return true; + } + l.clear(); + l << "0" << "off" << "false"; + if ( l.contains( val ) ) + { + *result = false; + return true; + } + return false; +} + +bool KateDocument::checkIntValue( QString val, int *result ) +{ + bool ret( false ); + *result = val.toInt( &ret ); + return ret; +} + +bool KateDocument::checkColorValue( QString val, QColor &c ) +{ + c.setNamedColor( val ); + return c.isValid(); +} + +// KTextEditor::variable +QString KateDocument::variable( const QString &name ) const +{ + if ( m_storedVariables.contains( name ) ) + return m_storedVariables[ name ]; + + return ""; +} + +//END + +void KateDocument::slotModOnHdDirty (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 1)) + { + // compare md5 with the one we have (if we have one) + if ( ! m_digest.isEmpty() ) + { + QCString tmp; + if ( createDigest( tmp ) && tmp == m_digest ) + return; + } + + m_modOnHd = true; + m_modOnHdReason = 1; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +void KateDocument::slotModOnHdCreated (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 2)) + { + m_modOnHd = true; + m_modOnHdReason = 2; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +void KateDocument::slotModOnHdDeleted (const QString &path) +{ + if ((path == m_dirWatchFile) && (!m_modOnHd || m_modOnHdReason != 3)) + { + m_modOnHd = true; + m_modOnHdReason = 3; + + // reenable dialog if not running atm + if (m_isasking == -1) + m_isasking = false; + + emit modifiedOnDisc (this, m_modOnHd, m_modOnHdReason); + } +} + +bool KateDocument::createDigest( QCString &result ) +{ + bool ret = false; + result = ""; + if ( url().isLocalFile() ) + { + QFile f ( url().path() ); + if ( f.open( IO_ReadOnly) ) + { + KMD5 md5; + ret = md5.update( f ); + md5.hexDigest( result ); + f.close(); + ret = true; + } + } + return ret; +} + +QString KateDocument::reasonedMOHString() const +{ + switch( m_modOnHdReason ) + { + case 1: + return i18n("The file '%1' was modified by another program.").arg( url().prettyURL() ); + break; + case 2: + return i18n("The file '%1' was created by another program.").arg( url().prettyURL() ); + break; + case 3: + return i18n("The file '%1' was deleted by another program.").arg( url().prettyURL() ); + break; + default: + return QString(); + } +} + +void KateDocument::removeTrailingSpace( uint line ) +{ + // remove trailing spaces from left line if required + if ( config()->configFlags() & KateDocumentConfig::cfRemoveTrailingDyn ) + { + KateTextLine::Ptr ln = kateTextLine( line ); + + if ( ! ln ) return; + + if ( line == activeView()->cursorLine() + && activeView()->cursorColumnReal() >= (uint)kMax(0,ln->lastChar()) ) + return; + + if ( ln->length() ) + { + uint p = ln->lastChar() + 1; + uint l = ln->length() - p; + if ( l ) + editRemoveText( line, p, l); + } + } +} + +void KateDocument::updateFileType (int newType, bool user) +{ + if (user || !m_fileTypeSetByUser) + { + const KateFileType *t = 0; + if ((newType == -1) || (t = KateFactory::self()->fileTypeManager()->fileType (newType))) + { + m_fileType = newType; + + if (t) + { + m_config->configStart(); + // views! + KateView *v; + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configStart(); + v->renderer()->config()->configStart(); + } + + readVariableLine( t->varLine ); + + m_config->configEnd(); + for (v = m_views.first(); v != 0L; v= m_views.next() ) + { + v->config()->configEnd(); + v->renderer()->config()->configEnd(); + } + } + } + } +} + +uint KateDocument::documentNumber () const +{ + return KTextEditor::Document::documentNumber (); +} + + + + +void KateDocument::slotQueryClose_save(bool *handled, bool* abortClosing) { + *handled=true; + *abortClosing=true; + if (m_url.isEmpty()) + { + KEncodingFileDialog::Result res=KEncodingFileDialog::getSaveURLAndEncoding(config()->encoding(), + QString::null,QString::null,0,i18n("Save File")); + + if( res.URLs.isEmpty() || !checkOverwrite( res.URLs.first() ) ) { + *abortClosing=true; + return; + } + setEncoding( res.encoding ); + saveAs( res.URLs.first() ); + *abortClosing=false; + } + else + { + save(); + *abortClosing=false; + } + +} + +bool KateDocument::checkOverwrite( KURL u ) +{ + if( !u.isLocalFile() ) + return true; + + QFileInfo info( u.path() ); + if( !info.exists() ) + return true; + + return KMessageBox::Cancel != KMessageBox::warningContinueCancel( 0, + i18n( "A file named \"%1\" already exists. " + "Are you sure you want to overwrite it?" ).arg( info.fileName() ), + i18n( "Overwrite File?" ), + i18n( "&Overwrite" ) ); +} + +void KateDocument::setDefaultEncoding (const QString &encoding) +{ + s_defaultEncoding = encoding; +} + +//BEGIN KTextEditor::TemplateInterface +bool KateDocument::insertTemplateTextImplementation ( uint line, uint column, const QString &templateString, const QMap<QString,QString> &initialValues, QWidget *) { + return (new KateTemplateHandler(this,line,column,templateString,initialValues))->initOk(); +} + +void KateDocument::testTemplateCode() { + int col=activeView()->cursorColumn(); + int line=activeView()->cursorLine(); + insertTemplateText(line,col,"for ${index} \\${NOPLACEHOLDER} ${index} ${blah} ${fullname} \\$${Placeholder} \\${${PLACEHOLDER2}}\n next line:${ANOTHERPLACEHOLDER} $${DOLLARBEFOREPLACEHOLDER} {NOTHING} {\n${cursor}\n}",QMap<QString,QString>()); +} + +bool KateDocument::invokeTabInterceptor(KKey key) { + if (m_tabInterceptor) return (*m_tabInterceptor)(key); + return false; +} + +bool KateDocument::setTabInterceptor(KateKeyInterceptorFunctor *interceptor) { + if (m_tabInterceptor) return false; + m_tabInterceptor=interceptor; + return true; +} + +bool KateDocument::removeTabInterceptor(KateKeyInterceptorFunctor *interceptor) { + if (m_tabInterceptor!=interceptor) return false; + m_tabInterceptor=0; + return true; +} +//END KTextEditor::TemplateInterface + +//BEGIN DEPRECATED STUFF + bool KateDocument::setSelection ( uint startLine, uint startCol, uint endLine, uint endCol ) +{ if (m_activeView) return m_activeView->setSelection (startLine, startCol, endLine, endCol); return false; } + + bool KateDocument::clearSelection () + { if (m_activeView) return m_activeView->clearSelection(); return false; } + + bool KateDocument::hasSelection () const + { if (m_activeView) return m_activeView->hasSelection (); return false; } + + QString KateDocument::selection () const + { if (m_activeView) return m_activeView->selection (); return QString(""); } + + bool KateDocument::removeSelectedText () + { if (m_activeView) return m_activeView->removeSelectedText (); return false; } + + bool KateDocument::selectAll() + { if (m_activeView) return m_activeView->selectAll (); return false; } + + int KateDocument::selStartLine() + { if (m_activeView) return m_activeView->selStartLine (); return 0; } + + int KateDocument::selStartCol() + { if (m_activeView) return m_activeView->selStartCol (); return 0; } + + int KateDocument::selEndLine() + { if (m_activeView) return m_activeView->selEndLine (); return 0; } + + int KateDocument::selEndCol() + { if (m_activeView) return m_activeView->selEndCol (); return 0; } + + bool KateDocument::blockSelectionMode () + { if (m_activeView) return m_activeView->blockSelectionMode (); return false; } + +bool KateDocument::setBlockSelectionMode (bool on) + { if (m_activeView) return m_activeView->setBlockSelectionMode (on); return false; } + +bool KateDocument::toggleBlockSelectionMode () + { if (m_activeView) return m_activeView->toggleBlockSelectionMode (); return false; } +//END DEPRECATED + +//END DEPRECATED STUFF + +// kate: space-indent on; indent-width 2; replace-tabs on; |