From ce4a32fe52ef09d8f5ff1dd22c001110902b60a2 Mon Sep 17 00:00:00 2001 From: toma Date: Wed, 25 Nov 2009 17:56:58 +0000 Subject: Copy the KDE 3.5 branch to branches/trinity for new KDE 3.5 features. BUG:215923 git-svn-id: svn://anonsvn.kde.org/home/kde/branches/trinity/kdelibs@1054174 283d02a7-25f6-0310-bc7c-ecb5cbfe19da --- kate/part/.kateconfig | 1 + kate/part/Makefile.am | 44 + kate/part/configure.in.in | 89 + kate/part/katearbitraryhighlight.cpp | 162 + kate/part/katearbitraryhighlight.h | 87 + kate/part/kateattribute.cpp | 268 ++ kate/part/kateattribute.h | 147 + kate/part/kateautoindent.cpp | 2543 +++++++++++++++ kate/part/kateautoindent.h | 581 ++++ kate/part/katebookmarks.cpp | 287 ++ kate/part/katebookmarks.h | 86 + kate/part/katebuffer.cpp | 1660 ++++++++++ kate/part/katebuffer.h | 709 +++++ kate/part/katecmds.cpp | 605 ++++ kate/part/katecmds.h | 178 ++ kate/part/katecodecompletion.cpp | 566 ++++ kate/part/katecodecompletion.h | 164 + kate/part/katecodefoldinghelpers.cpp | 1662 ++++++++++ kate/part/katecodefoldinghelpers.h | 222 ++ kate/part/kateconfig.cpp | 1429 +++++++++ kate/part/kateconfig.h | 537 ++++ kate/part/katecursor.cpp | 192 ++ kate/part/katecursor.h | 250 ++ kate/part/katedialogs.cpp | 1740 +++++++++++ kate/part/katedialogs.h | 406 +++ kate/part/katedocument.cpp | 5174 +++++++++++++++++++++++++++++++ kate/part/katedocument.h | 1073 +++++++ kate/part/katedocumenthelpers.cpp | 56 + kate/part/katedocumenthelpers.h | 71 + kate/part/katefactory.cpp | 276 ++ kate/part/katefactory.h | 312 ++ kate/part/katefiletype.cpp | 596 ++++ kate/part/katefiletype.h | 142 + kate/part/katefont.cpp | 127 + kate/part/katefont.h | 114 + kate/part/katehighlight.cpp | 3473 +++++++++++++++++++++ kate/part/katehighlight.h | 438 +++ kate/part/kateindentscriptabstracts.cpp | 49 + kate/part/kateindentscriptabstracts.h | 99 + kate/part/katejscript.cpp | 1169 +++++++ kate/part/katejscript.h | 233 ++ kate/part/katekeyinterceptorfunctor.h | 25 + kate/part/katelinerange.cpp | 75 + kate/part/katelinerange.h | 70 + kate/part/kateluaindentscript.cpp | 528 ++++ kate/part/kateluaindentscript.h | 69 + kate/part/kateprinter.cpp | 1005 ++++++ kate/part/kateprinter.h | 136 + kate/part/katerenderer.cpp | 1032 ++++++ kate/part/katerenderer.h | 272 ++ kate/part/kateschema.cpp | 1611 ++++++++++ kate/part/kateschema.h | 323 ++ kate/part/katesearch.cpp | 1012 ++++++ kate/part/katesearch.h | 243 ++ kate/part/katespell.cpp | 221 ++ kate/part/katespell.h | 86 + kate/part/katesupercursor.cpp | 746 +++++ kate/part/katesupercursor.h | 463 +++ kate/part/katesyntaxdocument.cpp | 475 +++ kate/part/katesyntaxdocument.h | 164 + kate/part/katetemplatehandler.cpp | 342 ++ kate/part/katetemplatehandler.h | 68 + kate/part/katetextline.cpp | 443 +++ kate/part/katetextline.h | 456 +++ kate/part/kateundo.cpp | 392 +++ kate/part/kateundo.h | 141 + kate/part/kateview.cpp | 1920 ++++++++++++ kate/part/kateview.h | 571 ++++ kate/part/kateviewhelpers.cpp | 1205 +++++++ kate/part/kateviewhelpers.h | 207 ++ kate/part/kateviewinternal.cpp | 3496 +++++++++++++++++++++ kate/part/kateviewinternal.h | 397 +++ kate/part/test_regression.cpp | 1344 ++++++++ kate/part/test_regression.h | 249 ++ 74 files changed, 47804 insertions(+) create mode 100644 kate/part/.kateconfig create mode 100644 kate/part/Makefile.am create mode 100644 kate/part/configure.in.in create mode 100644 kate/part/katearbitraryhighlight.cpp create mode 100644 kate/part/katearbitraryhighlight.h create mode 100644 kate/part/kateattribute.cpp create mode 100644 kate/part/kateattribute.h create mode 100644 kate/part/kateautoindent.cpp create mode 100644 kate/part/kateautoindent.h create mode 100644 kate/part/katebookmarks.cpp create mode 100644 kate/part/katebookmarks.h create mode 100644 kate/part/katebuffer.cpp create mode 100644 kate/part/katebuffer.h create mode 100644 kate/part/katecmds.cpp create mode 100644 kate/part/katecmds.h create mode 100644 kate/part/katecodecompletion.cpp create mode 100644 kate/part/katecodecompletion.h create mode 100644 kate/part/katecodefoldinghelpers.cpp create mode 100644 kate/part/katecodefoldinghelpers.h create mode 100644 kate/part/kateconfig.cpp create mode 100644 kate/part/kateconfig.h create mode 100644 kate/part/katecursor.cpp create mode 100644 kate/part/katecursor.h create mode 100644 kate/part/katedialogs.cpp create mode 100644 kate/part/katedialogs.h create mode 100644 kate/part/katedocument.cpp create mode 100644 kate/part/katedocument.h create mode 100644 kate/part/katedocumenthelpers.cpp create mode 100644 kate/part/katedocumenthelpers.h create mode 100644 kate/part/katefactory.cpp create mode 100644 kate/part/katefactory.h create mode 100644 kate/part/katefiletype.cpp create mode 100644 kate/part/katefiletype.h create mode 100644 kate/part/katefont.cpp create mode 100644 kate/part/katefont.h create mode 100644 kate/part/katehighlight.cpp create mode 100644 kate/part/katehighlight.h create mode 100644 kate/part/kateindentscriptabstracts.cpp create mode 100644 kate/part/kateindentscriptabstracts.h create mode 100644 kate/part/katejscript.cpp create mode 100644 kate/part/katejscript.h create mode 100644 kate/part/katekeyinterceptorfunctor.h create mode 100644 kate/part/katelinerange.cpp create mode 100644 kate/part/katelinerange.h create mode 100644 kate/part/kateluaindentscript.cpp create mode 100644 kate/part/kateluaindentscript.h create mode 100644 kate/part/kateprinter.cpp create mode 100644 kate/part/kateprinter.h create mode 100644 kate/part/katerenderer.cpp create mode 100644 kate/part/katerenderer.h create mode 100644 kate/part/kateschema.cpp create mode 100644 kate/part/kateschema.h create mode 100644 kate/part/katesearch.cpp create mode 100644 kate/part/katesearch.h create mode 100644 kate/part/katespell.cpp create mode 100644 kate/part/katespell.h create mode 100644 kate/part/katesupercursor.cpp create mode 100644 kate/part/katesupercursor.h create mode 100644 kate/part/katesyntaxdocument.cpp create mode 100644 kate/part/katesyntaxdocument.h create mode 100644 kate/part/katetemplatehandler.cpp create mode 100644 kate/part/katetemplatehandler.h create mode 100644 kate/part/katetextline.cpp create mode 100644 kate/part/katetextline.h create mode 100644 kate/part/kateundo.cpp create mode 100644 kate/part/kateundo.h create mode 100644 kate/part/kateview.cpp create mode 100644 kate/part/kateview.h create mode 100644 kate/part/kateviewhelpers.cpp create mode 100644 kate/part/kateviewhelpers.h create mode 100644 kate/part/kateviewinternal.cpp create mode 100644 kate/part/kateviewinternal.h create mode 100644 kate/part/test_regression.cpp create mode 100644 kate/part/test_regression.h (limited to 'kate/part') diff --git a/kate/part/.kateconfig b/kate/part/.kateconfig new file mode 100644 index 000000000..5b0885abe --- /dev/null +++ b/kate/part/.kateconfig @@ -0,0 +1 @@ +kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/Makefile.am b/kate/part/Makefile.am new file mode 100644 index 000000000..4a182452e --- /dev/null +++ b/kate/part/Makefile.am @@ -0,0 +1,44 @@ +kde_module_LTLIBRARIES = libkatepart.la + +noinst_LTLIBRARIES = libkate.la + +libkate_la_SOURCES = katesearch.cpp katebuffer.cpp katecmds.cpp \ + kateundo.cpp katecursor.cpp katedialogs.cpp katedocument.cpp \ + katefactory.cpp katehighlight.cpp katesyntaxdocument.cpp \ + katetextline.cpp kateview.cpp kateconfig.cpp kateviewhelpers.cpp \ + katecodecompletion.cpp katedocumenthelpers.cpp \ + katecodefoldinghelpers.cpp kateviewinternal.cpp katebookmarks.cpp \ + kateprinter.cpp katefont.cpp katelinerange.cpp katesupercursor.cpp \ + katearbitraryhighlight.cpp katerenderer.cpp kateattribute.cpp \ + kateautoindent.cpp katefiletype.cpp kateschema.cpp katedocument.skel \ + katetemplatehandler.cpp katejscript.cpp katespell.cpp kateindentscriptabstracts.cpp \ + kateluaindentscript.cpp + +libkatepart_la_SOURCES = dummy.cpp + +libkatepart_la_LIBADD = libkate.la ../interfaces/libkatepartinterfaces.la $(top_builddir)/kdeprint/libkdeprint.la $(top_builddir)/kutils/libkutils.la $(top_builddir)/kjs/libkjs.la $(LUA_LIBS) + +libkatepart_la_LDFLAGS = $(all_libraries) $(KDE_PLUGIN) + +INCLUDES= -I../interfaces -I$(top_srcdir) -I$(top_srcdir)/kdeprint -I$(top_srcdir)/interfaces -I$(top_srcdir)/interfaces/kregexpeditor -I$(top_srcdir)/kdefx -I$(top_srcdir)/kutils -I$(top_builddir)/kjs $(LUA_INCLUDES) $(all_includes) + +METASOURCES = AUTO + +LUT_FILES = katejscript.lut.h + +CREATE_HASH_TABLE = $(top_srcdir)/kjs/create_hash_table + +dummy.cpp: $(srcdir)/Makefile.am + touch $@ + +katejscript.lut.h : $(srcdir)/katejscript.cpp $(CREATE_HASH_TABLE) + $(PERL) $(CREATE_HASH_TABLE) $(srcdir)/katejscript.cpp > $@ +katejscript.lo: katejscript.lut.h + +CLEANFILES = $(LUT_FILES) + +## test program +TESTS = testkateregression +check_PROGRAMS = testkateregression +testkateregression_SOURCES = test_regression.cpp +testkateregression_LDADD = $(libkatepart_la_LIBADD) diff --git a/kate/part/configure.in.in b/kate/part/configure.in.in new file mode 100644 index 000000000..faf0717e9 --- /dev/null +++ b/kate/part/configure.in.in @@ -0,0 +1,89 @@ +AC_DEFUN([AC_PATH_LUA], [ + +dnl Based on the lua check used by yzis-M3 + + HAVE_LUA="" + + AC_ARG_WITH([lua], + AC_HELP_STRING([--without-lua], [Build without Lua libraries (default: check)])) + + AC_ARG_WITH(lua-dir, + AC_HELP_STRING([--with-lua-dir=DIR],[where the root of Lua 5.x is installed]), + [ + LUA="$withval" + LUA_INCLUDES=-I"$withval"/include + LUA_LIBS="-L$withval/lib" ]) + + AC_ARG_WITH(lua-includes, + AC_HELP_STRING([--with-lua-includes=DIR],[where the Lua includes are]), + [ LUA_INCLUDES="-I$withval" ]) + + AC_ARG_WITH(lua-libraries, + AC_HELP_STRING([--with-lua-libraries=DIR],[where the Lua library is installed]), + [ + LUA_LIBS="-L$withval" ]) + + + if test "x$with_lua" = "xno"; then + AC_MSG_RESULT([Not using Lua]) + else + if ! test "x$LUA" = "x"; then + AC_MSG_RESULT(using Lua from $LUA) + fi + if ! test "x$LUA_LIBS" = "x"; then + AC_MSG_RESULT(using Lua libraries in $LUA_LIBS) + fi + if ! test "x$LUA_INCLUDES" = "x"; then + AC_MSG_RESULT(using Lua includes in $LUA_INCLUDES) + fi + + dnl checking some headers first + ac_save_CFLAGS="$CFLAGS" + ac_save_CPPFLAGS="$CPPFLAGS" + ac_save_LDFLAGS="$LDFLAGS" + CFLAGS="$LUA_INCLUDES $CFLAGS" + CPPFLAGS="$LUA_INCLUDES $CPPFLAGS" + LDFLAGS="$LUA_LIBS $LDFLAGS" + + LUAH_FOUND="" + AC_CHECK_HEADER(lua.h,LUAH_FOUND="true", + [ AC_MSG_RESULT([lua.h was not found or was not usable, Lua 5.0 headers are required !]) ] + ) + LUALIBH_FOUND="" + AC_CHECK_HEADER(lualib.h,LUALIBH_FOUND="true", + [ AC_MSG_RESULT([lualib.h was not found or was not usable, Lua 5.0 headers are required !]) ] + ) + + + dnl find the libs name + if test -z "$LUALIBH_FOUND" -o -z "$LUAH_FOUND"; then + LUA_LIBS="" + else + AC_CHECK_LIB(lua50,lua_version, LUA_LIBS="$LUA_LIBS -llua50 -llualib50", + AC_CHECK_LIB(lua,lua_version, LUA_LIBS="$LUA_LIBS -llua -llualib", + [LUA_LIBS="" + AC_MSG_RESULT([Lua 5.0 libraries were not found !]) ] + ) + ) + fi + CFLAGS="$ac_save_CFLAGS" + CPPFLAGS="$ac_save_CPPFLAGS" + LDFLAGS="$ac_save_LDFLAGS" + + + if test -z "$LUA_LIBS"; then + LUA="" + LUA_INCLUDES="" + LUA_LIBS="" + else + AC_DEFINE_UNQUOTED(HAVE_LUA, 1, [Define if you have LUA > 5.0]) + HAVE_LUA="yes" + fi + AC_SUBST(LUA) + AC_SUBST(LUA_INCLUDES) + AC_SUBST(LUA_LIBS) + fi + +]) + +AC_PATH_LUA diff --git a/kate/part/katearbitraryhighlight.cpp b/kate/part/katearbitraryhighlight.cpp new file mode 100644 index 000000000..2ecbececc --- /dev/null +++ b/kate/part/katearbitraryhighlight.cpp @@ -0,0 +1,162 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda + + 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 02110-1301, USA. +*/ + +#include "katearbitraryhighlight.h" +#include "katearbitraryhighlight.moc" + +#include "katesupercursor.h" +#include "katedocument.h" + +#include + +#include + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateSuperCursor* start, +KateSuperCursor* end, QObject* parent, const char* name) : +KateSuperRange(start, end, parent, name) { +} + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateDocument* doc, const KateRange& range, QObject* parent, const char* name) + : KateSuperRange(doc, range, parent, name) +{ +} + +KateArbitraryHighlightRange::KateArbitraryHighlightRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent, const char* name) + : KateSuperRange(doc, start, end, parent, name) +{ +} + +KateArbitraryHighlightRange::~KateArbitraryHighlightRange() +{ +} + +KateArbitraryHighlight::KateArbitraryHighlight(KateDocument* parent, const char* name) + : QObject(parent, name) +{ +} + +KateAttribute KateArbitraryHighlightRange::merge(QPtrList ranges) +{ + ranges.sort(); + + KateAttribute ret; + + if (ranges.first() && ranges.current()->inherits("KateArbitraryHighlightRange")) + ret = *(static_cast(ranges.current())); + + KateSuperRange* r; + while ((r = ranges.next())) { + if (r->inherits("KateArbitraryHighlightRange")) { + KateArbitraryHighlightRange* hl = static_cast(r); + ret += *hl; + } + } + + return ret; +} + +void KateArbitraryHighlight::addHighlightToDocument(KateSuperRangeList* list) +{ + m_docHLs.append(list); + connect(list, SIGNAL(rangeEliminated(KateSuperRange*)), SLOT(slotRangeEliminated(KateSuperRange*))); + connect(list, SIGNAL(destroyed(QObject*)),SLOT(slotRangeListDeleted(QObject*))); +} + +void KateArbitraryHighlight::addHighlightToView(KateSuperRangeList* list, KateView* view) +{ + if (!m_viewHLs[view]) + m_viewHLs.insert(view, new QPtrList()); + + m_viewHLs[view]->append(list); + + connect(list, SIGNAL(rangeEliminated(KateSuperRange*)), SLOT(slotTagRange(KateSuperRange*))); + connect(list, SIGNAL(tagRange(KateSuperRange*)), SLOT(slotTagRange(KateSuperRange*))); + connect(list, SIGNAL(destroyed(QObject*)),SLOT(slotRangeListDeleted(QObject*))); +} + +void KateArbitraryHighlight::slotRangeListDeleted(QObject* obj) { + int id=m_docHLs.findRef(static_cast(obj)); + if (id>=0) m_docHLs.take(id); + + for (QMap* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l==obj) { + l->take(); + break; //should we check if one list is stored more than once for a view ?? I don't think adding the same list 2 or more times is sane, but who knows (jowenn) + } +} + +KateSuperRangeList& KateArbitraryHighlight::rangesIncluding(uint line, KateView* view) +{ + // OPTIMISE make return value persistent + + static KateSuperRangeList s_return(false); + + Q_ASSERT(!s_return.autoDelete()); + s_return.clear(); + + //--- TEMPORARY OPTIMISATION: return the actual range when there are none or one. --- + if (m_docHLs.count() + m_viewHLs.count() == 0) + return s_return; + else if (m_docHLs.count() + m_viewHLs.count() == 1) + if (m_docHLs.count()) + return *(m_docHLs.first()); + else + if (m_viewHLs.values().first() && m_viewHLs.values().first()->count() == 1) + if (m_viewHLs.keys().first() == view && m_viewHLs.values().first()) + return *(m_viewHLs.values().first()->first()); + //--- END Temporary optimisation --- + + if (view) { + QPtrList* list = m_viewHLs[view]; + if (list) + for (KateSuperRangeList* l = list->first(); l; l = list->next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + + } else { + for (QMap* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + } + + for (KateSuperRangeList* l = m_docHLs.first(); l; l = m_docHLs.next()) + if (l->count()) + s_return.appendList(l->rangesIncluding(line)); + + return s_return; +} + +void KateArbitraryHighlight::slotTagRange(KateSuperRange* range) +{ + emit tagLines(viewForRange(range), range); +} + +KateView* KateArbitraryHighlight::viewForRange(KateSuperRange* range) +{ + for (QMap* >::Iterator it = m_viewHLs.begin(); it != m_viewHLs.end(); ++it) + for (KateSuperRangeList* l = (*it)->first(); l; l = (*it)->next()) + if (l->contains(range)) + return it.key(); + + // This must belong to a document-global highlight + return 0L; +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katearbitraryhighlight.h b/kate/part/katearbitraryhighlight.h new file mode 100644 index 000000000..7955e7889 --- /dev/null +++ b/kate/part/katearbitraryhighlight.h @@ -0,0 +1,87 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda + + 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 02110-1301, USA. +*/ + +#ifndef KATEARBITRARYHIGHLIGHT_H +#define KATEARBITRARYHIGHLIGHT_H + +#include "kateattribute.h" +#include "katesupercursor.h" + +#include +#include +#include + +class KateDocument; +class KateView; + +class KateArbitraryHighlightRange : public KateSuperRange, public KateAttribute +{ + Q_OBJECT + +public: + KateArbitraryHighlightRange(KateSuperCursor* start, KateSuperCursor* end, QObject* parent = 0L, const char* name = 0L); + KateArbitraryHighlightRange(KateDocument* doc, const KateRange& range, QObject* parent = 0L, const char* name = 0L); + KateArbitraryHighlightRange(KateDocument* doc, const KateTextCursor& start, const KateTextCursor& end, QObject* parent = 0L, const char* name = 0L); + + virtual ~KateArbitraryHighlightRange(); + + virtual void changed() { slotTagRange(); }; + + static KateAttribute merge(QPtrList ranges); +}; + +/** + * An arbitrary highlighting interface for Kate. + * + * Ideas for more features: + * - integration with syntax highlighting: + * - eg. a signal for when a new context is created, destroyed, changed + * - hopefully make this extension more complimentary to the current syntax highlighting + * - signal for cursor movement + * - signal for mouse movement + * - identical highlight for whole list + * - signals for view movement + */ +class KateArbitraryHighlight : public QObject +{ + Q_OBJECT + +public: + KateArbitraryHighlight(KateDocument* parent = 0L, const char* name = 0L); + + void addHighlightToDocument(KateSuperRangeList* list); + void addHighlightToView(KateSuperRangeList* list, KateView* view); + + KateSuperRangeList& rangesIncluding(uint line, KateView* view = 0L); + +signals: + void tagLines(KateView* view, KateSuperRange* range); + +private slots: + void slotTagRange(KateSuperRange* range); + void slotRangeListDeleted(QObject* obj); +private: + KateView* viewForRange(KateSuperRange* range); + + QMap* > m_viewHLs; + QPtrList m_docHLs; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateattribute.cpp b/kate/part/kateattribute.cpp new file mode 100644 index 000000000..5de93a406 --- /dev/null +++ b/kate/part/kateattribute.cpp @@ -0,0 +1,268 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda + + 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 02110-1301, USA. +*/ + +#include "kateattribute.h" + +KateAttribute::KateAttribute() + : m_weight(QFont::Normal) + , m_italic(false) + , m_underline(false) + , m_overline(false) + , m_strikeout(false) + , m_itemsSet(0) + +{ +} + +KateAttribute::~KateAttribute() +{ +} + +void KateAttribute::clear() +{ + m_itemsSet=0; +} + +KateAttribute& KateAttribute::operator+=(const KateAttribute& a) +{ + if (a.itemSet(Weight)) + setWeight(a.weight()); + + if (a.itemSet(Italic)) + setItalic(a.italic()); + + if (a.itemSet(Underline)) + setUnderline(a.underline()); + + if (a.itemSet(Overline)) + setOverline(a.overline()); + + if (a.itemSet(StrikeOut)) + setStrikeOut(a.strikeOut()); + + if (a.itemSet(Outline)) + setOutline(a.outline()); + + if (a.itemSet(TextColor)) + setTextColor(a.textColor()); + + if (a.itemSet(SelectedTextColor)) + setSelectedTextColor(a.selectedTextColor()); + + if (a.itemSet(BGColor)) + setBGColor(a.bgColor()); + + if (a.itemSet(SelectedBGColor)) + setSelectedBGColor(a.selectedBGColor()); + + return *this; +} + +QFont KateAttribute::font(const QFont& ref) +{ + QFont ret = ref; + + if (itemSet(Weight)) + ret.setWeight(weight()); + if (itemSet(Italic)) + ret.setItalic(italic()); + if (itemSet(Underline)) + ret.setUnderline(underline()); + if (itemSet(Overline)) + ret.setOverline(overline()); + if (itemSet(StrikeOut)) + ret.setStrikeOut(strikeOut()); + + return ret; +} + +void KateAttribute::setWeight(int weight) +{ + if (!(m_itemsSet & Weight) || m_weight != weight) + { + m_itemsSet |= Weight; + + m_weight = weight; + + changed(); + } +} + +void KateAttribute::setBold(bool enable) +{ + setWeight(enable ? QFont::Bold : QFont::Normal); +} + +void KateAttribute::setItalic(bool enable) +{ + if (!(m_itemsSet & Italic) || m_italic != enable) + { + m_itemsSet |= Italic; + + m_italic = enable; + + changed(); + } +} + +void KateAttribute::setUnderline(bool enable) +{ + if (!(m_itemsSet & Underline) || m_underline != enable) + { + m_itemsSet |= Underline; + + m_underline = enable; + + changed(); + } +} + +void KateAttribute::setOverline(bool enable) +{ + if (!(m_itemsSet & Overline) || m_overline != enable) + { + m_itemsSet |= Overline; + + m_overline = enable; + + changed(); + } +} + +void KateAttribute::setStrikeOut(bool enable) +{ + if (!(m_itemsSet & StrikeOut) || m_strikeout != enable) + { + m_itemsSet |= StrikeOut; + + m_strikeout = enable; + + changed(); + } +} + +void KateAttribute::setOutline(const QColor& color) +{ + if (!(m_itemsSet & Outline) || m_outline != color) + { + m_itemsSet |= Outline; + + m_outline = color; + + changed(); + } +} + +void KateAttribute::setTextColor(const QColor& color) +{ + if (!(m_itemsSet & TextColor) || m_textColor != color) + { + m_itemsSet |= TextColor; + + m_textColor = color; + + changed(); + } +} + +void KateAttribute::setSelectedTextColor(const QColor& color) +{ + if (!(m_itemsSet & SelectedTextColor) || m_selectedTextColor != color) + { + m_itemsSet |= SelectedTextColor; + + m_selectedTextColor = color; + + changed(); + } +} + +void KateAttribute::setBGColor(const QColor& color) +{ + if (!(m_itemsSet & BGColor) || m_bgColor != color) + { + m_itemsSet |= BGColor; + + m_bgColor = color; + + changed(); + } +} + +void KateAttribute::setSelectedBGColor(const QColor& color) +{ + if (!(m_itemsSet & SelectedBGColor) || m_selectedBGColor != color) + { + m_itemsSet |= SelectedBGColor; + + m_selectedBGColor = color; + + changed(); + } +} + +bool operator ==(const KateAttribute& h1, const KateAttribute& h2) +{ + if (h1.m_itemsSet != h2.m_itemsSet) + return false; + + if (h1.itemSet(KateAttribute::Weight)) + if (h1.m_weight != h2.m_weight) + return false; + + if (h1.itemSet(KateAttribute::Italic)) + if (h1.m_italic != h2.m_italic) + return false; + + if (h1.itemSet(KateAttribute::Underline)) + if (h1.m_underline != h2.m_underline) + return false; + + if (h1.itemSet(KateAttribute::StrikeOut)) + if (h1.m_strikeout != h2.m_strikeout) + return false; + + if (h1.itemSet(KateAttribute::Outline)) + if (h1.m_outline != h2.m_outline) + return false; + + if (h1.itemSet(KateAttribute::TextColor)) + if (h1.m_textColor != h2.m_textColor) + return false; + + if (h1.itemSet(KateAttribute::SelectedTextColor)) + if (h1.m_selectedTextColor != h2.m_selectedTextColor) + return false; + + if (h1.itemSet(KateAttribute::BGColor)) + if (h1.m_bgColor != h2.m_bgColor) + return false; + + if (h1.itemSet(KateAttribute::SelectedBGColor)) + if (h1.m_selectedBGColor != h2.m_selectedBGColor) + return false; + + return true; +} + +bool operator !=(const KateAttribute& h1, const KateAttribute& h2) +{ + return !(h1 == h2); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateattribute.h b/kate/part/kateattribute.h new file mode 100644 index 000000000..c4933e690 --- /dev/null +++ b/kate/part/kateattribute.h @@ -0,0 +1,147 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Hamish Rodda + + 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 02110-1301, USA. +*/ + +#ifndef __KATE_ATTRIBUTE_H__ +#define __KATE_ATTRIBUTE_H__ + +#include "katefont.h" + +#include + +/** + * The Attribute class incorporates all text decorations supported by Kate. + * + * TODO: store the actual font as well. + * TODO: update changed mechanism - use separate bitfield + */ +class KateAttribute +{ +public: + enum items { + Weight = 0x1, + Bold = 0x2, + Italic = 0x4, + Underline = 0x8, + StrikeOut = 0x10, + Outline = 0x20, + TextColor = 0x40, + SelectedTextColor = 0x80, + BGColor = 0x100, + SelectedBGColor = 0x200, + Overline = 0x400 + }; + + KateAttribute(); + virtual ~KateAttribute(); + + QFont font(const QFont& ref); + + inline int width(KateFontStruct& fs, const QString& text, int col, int tabWidth) const + { return fs.width(text, col, bold(), italic(), tabWidth); }; + + // Non-preferred function when you have a string and you want one char's width!! + inline int width(KateFontStruct& fs, const QChar& c, int tabWidth) const + { return fs.width(c, bold(), italic(), tabWidth); }; + + inline bool itemSet(int item) const + { return item & m_itemsSet; }; + + inline bool isSomethingSet() const + { return m_itemsSet; }; + + inline int itemsSet() const + { return m_itemsSet; }; + + inline void clearAttribute(int item) + { m_itemsSet &= (~item); } + + inline int weight() const + { return m_weight; }; + + void setWeight(int weight); + + inline bool bold() const + { return weight() >= QFont::Bold; }; + + void setBold(bool enable = true); + + inline bool italic() const + { return m_italic; }; + + void setItalic(bool enable = true); + + inline bool overline() const + { return m_overline; }; + + void setOverline(bool enable = true); + + inline bool underline() const + { return m_underline; }; + + void setUnderline(bool enable = true); + + inline bool strikeOut() const + { return m_strikeout; }; + + void setStrikeOut(bool enable = true); + + inline const QColor& outline() const + { return m_outline; }; + + void setOutline(const QColor& color); + + inline const QColor& textColor() const + { return m_textColor; }; + + void setTextColor(const QColor& color); + + inline const QColor& selectedTextColor() const + { return m_selectedTextColor; }; + + void setSelectedTextColor(const QColor& color); + + inline const QColor& bgColor() const + { return m_bgColor; }; + + void setBGColor(const QColor& color); + + inline const QColor& selectedBGColor() const + { return m_selectedBGColor; }; + + void setSelectedBGColor(const QColor& color); + + KateAttribute& operator+=(const KateAttribute& a); + + friend bool operator ==(const KateAttribute& h1, const KateAttribute& h2); + friend bool operator !=(const KateAttribute& h1, const KateAttribute& h2); + + virtual void changed() { m_changed = true; }; + bool isChanged() { bool ret = m_changed; m_changed = false; return ret; }; + + void clear(); + +private: + int m_weight; + bool m_italic, m_underline, m_overline,m_strikeout, m_changed; + QColor m_outline, m_textColor, m_selectedTextColor, m_bgColor, m_selectedBGColor; + int m_itemsSet; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateautoindent.cpp b/kate/part/kateautoindent.cpp new file mode 100644 index 000000000..7c58b6051 --- /dev/null +++ b/kate/part/kateautoindent.cpp @@ -0,0 +1,2543 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Jesse Yurkovich + Copyright (C) 2004 >Anders Lund (KateVarIndent class) + Copyright (C) 2005 Dominik Haumann (basic support for config page) + + 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 02110-1301, USA. +*/ + +#include "kateautoindent.h" +#include "kateautoindent.moc" + +#include "kateconfig.h" +#include "katehighlight.h" +#include "katefactory.h" +#include "katejscript.h" +#include "kateview.h" + +#include +#include +#include + +#include + +//BEGIN KateAutoIndent + +KateAutoIndent *KateAutoIndent::createIndenter (KateDocument *doc, uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return new KateNormalIndent (doc); + else if (mode == KateDocumentConfig::imCStyle) + return new KateCSmartIndent (doc); + else if (mode == KateDocumentConfig::imPythonStyle) + return new KatePythonIndent (doc); + else if (mode == KateDocumentConfig::imXmlStyle) + return new KateXmlIndent (doc); + else if (mode == KateDocumentConfig::imCSAndS) + return new KateCSAndSIndent (doc); + else if ( mode == KateDocumentConfig::imVarIndent ) + return new KateVarIndent ( doc ); +// else if ( mode == KateDocumentConfig::imScriptIndent) +// return new KateScriptIndent ( doc ); + + return new KateAutoIndent (doc); +} + +QStringList KateAutoIndent::listModes () +{ + QStringList l; + + l << modeDescription(KateDocumentConfig::imNone); + l << modeDescription(KateDocumentConfig::imNormal); + l << modeDescription(KateDocumentConfig::imCStyle); + l << modeDescription(KateDocumentConfig::imPythonStyle); + l << modeDescription(KateDocumentConfig::imXmlStyle); + l << modeDescription(KateDocumentConfig::imCSAndS); + l << modeDescription(KateDocumentConfig::imVarIndent); +// l << modeDescription(KateDocumentConfig::imScriptIndent); + + return l; +} + +QString KateAutoIndent::modeName (uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return QString ("normal"); + else if (mode == KateDocumentConfig::imCStyle) + return QString ("cstyle"); + else if (mode == KateDocumentConfig::imPythonStyle) + return QString ("python"); + else if (mode == KateDocumentConfig::imXmlStyle) + return QString ("xml"); + else if (mode == KateDocumentConfig::imCSAndS) + return QString ("csands"); + else if ( mode == KateDocumentConfig::imVarIndent ) + return QString( "varindent" ); +// else if ( mode == KateDocumentConfig::imScriptIndent ) +// return QString( "scriptindent" ); + + return QString ("none"); +} + +QString KateAutoIndent::modeDescription (uint mode) +{ + if (mode == KateDocumentConfig::imNormal) + return i18n ("Normal"); + else if (mode == KateDocumentConfig::imCStyle) + return i18n ("C Style"); + else if (mode == KateDocumentConfig::imPythonStyle) + return i18n ("Python Style"); + else if (mode == KateDocumentConfig::imXmlStyle) + return i18n ("XML Style"); + else if (mode == KateDocumentConfig::imCSAndS) + return i18n ("S&S C Style"); + else if ( mode == KateDocumentConfig::imVarIndent ) + return i18n("Variable Based Indenter"); +// else if ( mode == KateDocumentConfig::imScriptIndent ) +// return i18n("JavaScript Indenter"); + + return i18n ("None"); +} + +uint KateAutoIndent::modeNumber (const QString &name) +{ + if (modeName(KateDocumentConfig::imNormal) == name) + return KateDocumentConfig::imNormal; + else if (modeName(KateDocumentConfig::imCStyle) == name) + return KateDocumentConfig::imCStyle; + else if (modeName(KateDocumentConfig::imPythonStyle) == name) + return KateDocumentConfig::imPythonStyle; + else if (modeName(KateDocumentConfig::imXmlStyle) == name) + return KateDocumentConfig::imXmlStyle; + else if (modeName(KateDocumentConfig::imCSAndS) == name) + return KateDocumentConfig::imCSAndS; + else if ( modeName( KateDocumentConfig::imVarIndent ) == name ) + return KateDocumentConfig::imVarIndent; +// else if ( modeName( KateDocumentConfig::imScriptIndent ) == name ) +// return KateDocumentConfig::imScriptIndent; + + return KateDocumentConfig::imNone; +} + +bool KateAutoIndent::hasConfigPage (uint mode) +{ +// if ( mode == KateDocumentConfig::imScriptIndent ) +// return true; + + return false; +} + +IndenterConfigPage* KateAutoIndent::configPage(QWidget *parent, uint mode) +{ +// if ( mode == KateDocumentConfig::imScriptIndent ) +// return new ScriptIndentConfigPage(parent, "script_indent_config_page"); + + return 0; +} + +KateAutoIndent::KateAutoIndent (KateDocument *_doc) +: QObject(), doc(_doc) +{ +} +KateAutoIndent::~KateAutoIndent () +{ +} + +//END KateAutoIndent + +//BEGIN KateViewIndentAction +KateViewIndentationAction::KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject* parent, const char* name) + : KActionMenu (text, parent, name), doc(_doc) +{ + connect(popupMenu(),SIGNAL(aboutToShow()),this,SLOT(slotAboutToShow())); +} + +void KateViewIndentationAction::slotAboutToShow() +{ + QStringList modes = KateAutoIndent::listModes (); + + popupMenu()->clear (); + for (uint z=0; zinsertItem ( '&' + KateAutoIndent::modeDescription(z).replace('&', "&&"), this, SLOT(setMode(int)), 0, z); + + popupMenu()->setItemChecked (doc->config()->indentationMode(), true); +} + +void KateViewIndentationAction::setMode (int mode) +{ + doc->config()->setIndentationMode((uint)mode); +} +//END KateViewIndentationAction + +//BEGIN KateNormalIndent + +KateNormalIndent::KateNormalIndent (KateDocument *_doc) + : KateAutoIndent (_doc) +{ + // if highlighting changes, update attributes + connect(_doc, SIGNAL(hlChanged()), this, SLOT(updateConfig())); +} + +KateNormalIndent::~KateNormalIndent () +{ +} + +void KateNormalIndent::updateConfig () +{ + KateDocumentConfig *config = doc->config(); + + useSpaces = config->configFlags() & KateDocument::cfSpaceIndent || config->configFlags() & KateDocumentConfig::cfReplaceTabsDyn; + mixedIndent = useSpaces && config->configFlags() & KateDocumentConfig::cfMixedIndent; + keepProfile = config->configFlags() & KateDocument::cfKeepIndentProfile; + tabWidth = config->tabWidth(); + indentWidth = useSpaces? config->indentationWidth() : tabWidth; + + commentAttrib = 255; + doxyCommentAttrib = 255; + regionAttrib = 255; + symbolAttrib = 255; + alertAttrib = 255; + tagAttrib = 255; + wordAttrib = 255; + keywordAttrib = 255; + normalAttrib = 255; + extensionAttrib = 255; + preprocessorAttrib = 255; + stringAttrib = 255; + charAttrib = 255; + + KateHlItemDataList items; + doc->highlight()->getKateHlItemDataListCopy (0, items); + + for (uint i=0; iname; + if (name.find("Comment") != -1 && commentAttrib == 255) + { + commentAttrib = i; + } + else if (name.find("Region Marker") != -1 && regionAttrib == 255) + { + regionAttrib = i; + } + else if (name.find("Symbol") != -1 && symbolAttrib == 255) + { + symbolAttrib = i; + } + else if (name.find("Alert") != -1) + { + alertAttrib = i; + } + else if (name.find("Comment") != -1 && commentAttrib != 255 && doxyCommentAttrib == 255) + { + doxyCommentAttrib = i; + } + else if (name.find("Tags") != -1 && tagAttrib == 255) + { + tagAttrib = i; + } + else if (name.find("Word") != -1 && wordAttrib == 255) + { + wordAttrib = i; + } + else if (name.find("Keyword") != -1 && keywordAttrib == 255) + { + keywordAttrib = i; + } + else if (name.find("Normal") != -1 && normalAttrib == 255) + { + normalAttrib = i; + } + else if (name.find("Extensions") != -1 && extensionAttrib == 255) + { + extensionAttrib = i; + } + else if (name.find("Preprocessor") != -1 && preprocessorAttrib == 255) + { + preprocessorAttrib = i; + } + else if (name.find("String") != -1 && stringAttrib == 255) + { + stringAttrib = i; + } + else if (name.find("Char") != -1 && charAttrib == 255) + { + charAttrib = i; + } + } +} + +bool KateNormalIndent::isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close, uint &pos) const +{ + int parenOpen = 0; + bool atLeastOne = false; + bool getNext = false; + + pos = doc->plainKateTextLine(begin.line())->firstChar(); + + // Iterate one-by-one finding opening and closing chars + // Assume that open and close are 'Symbol' characters + while (begin < end) + { + QChar c = begin.currentChar(); + if (begin.currentAttrib() == symbolAttrib) + { + if (c == open) + { + if (!atLeastOne) + { + atLeastOne = true; + getNext = true; + pos = measureIndent(begin) + 1; + } + parenOpen++; + } + else if (c == close) + { + parenOpen--; + } + } + else if (getNext && !c.isSpace()) + { + getNext = false; + pos = measureIndent(begin); + } + + if (atLeastOne && parenOpen <= 0) + return true; + + if (!begin.moveForward(1)) + break; + } + + return (atLeastOne) ? false : true; +} + +bool KateNormalIndent::skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const +{ + int curLine = cur.line(); + if (newline) + cur.moveForward(1); + + if (cur >= max) + return false; + + do + { + uchar attrib = cur.currentAttrib(); + const QString hlFile = doc->highlight()->hlKeyForAttrib( attrib ); + + if (attrib != commentAttrib && attrib != regionAttrib && attrib != alertAttrib && attrib != preprocessorAttrib && !hlFile.endsWith("doxygen.xml")) + { + QChar c = cur.currentChar(); + if (!c.isNull() && !c.isSpace()) + break; + } + + if (!cur.moveForward(1)) + { + // not able to move forward, so set cur to max + cur = max; + break; + } + // Make sure col is 0 if we spill into next line i.e. count the '\n' as a character + if (curLine != cur.line()) + { + if (!newline) + break; + curLine = cur.line(); + cur.setCol(0); + } + } while (cur < max); + + if (cur > max) + cur = max; + return true; +} + +uint KateNormalIndent::measureIndent (KateDocCursor &cur) const +{ + // We cannot short-cut by checking for useSpaces because there may be + // tabs in the line despite this setting. + + return doc->plainKateTextLine(cur.line())->cursorX(cur.col(), tabWidth); +} + +QString KateNormalIndent::tabString(uint pos) const +{ + QString s; + pos = kMin (pos, 80U); // sanity check for large values of pos + + if (!useSpaces || mixedIndent) + { + while (pos >= tabWidth) + { + s += '\t'; + pos -= tabWidth; + } + } + while (pos > 0) + { + s += ' '; + pos--; + } + return s; +} + +void KateNormalIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) +{ + int line = begin.line() - 1; + int pos = begin.col(); + + while ((line > 0) && (pos < 0)) // search a not empty text line + pos = doc->plainKateTextLine(--line)->firstChar(); + + if (pos > 0) + { + QString filler = doc->text(line, 0, line, pos); + doc->insertText(begin.line(), 0, filler); + begin.setCol(filler.length()); + } + else + begin.setCol(0); +} + +//END + +//BEGIN KateCSmartIndent + +KateCSmartIndent::KateCSmartIndent (KateDocument *doc) +: KateNormalIndent (doc), + allowSemi (false), + processingBlock (false) +{ + kdDebug(13030)<<"CREATING KATECSMART INTDETER"<plainKateTextLine(line.line()); + + int firstChar = textLine->firstChar(); + // Empty line is worthless ... but only when doing more than 1 line + if (firstChar == -1 && processingBlock) + return; + + uint indent = 0; + + // TODO Here we do not check for beginning and ending comments ... + QChar first = textLine->getChar(firstChar); + QChar last = textLine->getChar(textLine->lastChar()); + + if (first == '}') + { + indent = findOpeningBrace(line); + } + else if (first == ')') + { + indent = findOpeningParen(line); + } + else if (first == '{') + { + // If this is the first brace, we keep the indent at 0 + KateDocCursor temp(line.line(), firstChar, doc); + if (!firstOpeningBrace(temp)) + indent = calcIndent(temp, false); + } + else if (first == ':') + { + // Initialization lists (handle c++ and c#) + int pos = findOpeningBrace(line); + if (pos == 0) + indent = indentWidth; + else + indent = pos + (indentWidth * 2); + } + else if (last == ':') + { + if (textLine->stringAtPos (firstChar, "case") || + textLine->stringAtPos (firstChar, "default") || + textLine->stringAtPos (firstChar, "public") || + textLine->stringAtPos (firstChar, "private") || + textLine->stringAtPos (firstChar, "protected") || + textLine->stringAtPos (firstChar, "signals") || + textLine->stringAtPos (firstChar, "Q_SIGNALS") || + textLine->stringAtPos (firstChar, "Q_SLOTS") || + textLine->stringAtPos (firstChar, "slots")) + { + indent = findOpeningBrace(line) + indentWidth; + } + } + else if (first == '*') + { + if (last == '/') + { + int lineEnd = textLine->lastChar(); + if (lineEnd > 0 && textLine->getChar(lineEnd - 1) == '*') + { + indent = findOpeningComment(line); + if (textLine->attribute(firstChar) == doxyCommentAttrib) + indent++; + } + else + return; + } + else + { + KateDocCursor temp = line; + if (textLine->attribute(firstChar) == doxyCommentAttrib) + indent = calcIndent(temp, false) + 1; + else + indent = calcIndent(temp, true); + } + } + else if (first == '#') + { + // c# regions + if (textLine->stringAtPos (firstChar, "#region") || + textLine->stringAtPos (firstChar, "#endregion")) + { + KateDocCursor temp = line; + indent = calcIndent(temp, true); + } + } + else + { + // Everything else ... + if (first == '/' && last != '/') + return; + + KateDocCursor temp = line; + indent = calcIndent(temp, true); + if (indent == 0) + { + KateNormalIndent::processNewline(line, true); + return; + } + } + + // Slightly faster if we don't indent what we don't have to + if (indent != measureIndent(line) || first == '}' || first == '{' || first == '#') + { + doc->removeText(line.line(), 0, line.line(), firstChar); + QString filler = tabString(indent); + if (indent > 0) doc->insertText(line.line(), 0, filler); + if (!processingBlock) line.setCol(filler.length()); + } +} + +void KateCSmartIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + kdDebug(13030)<<"PROCESS SECTION"< 0) ? true : false; + + while (cur.line() <= end.line()) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } + + processingBlock = false; + kdDebug(13030) << "+++ total: " << t.elapsed() << endl; +} + +bool KateCSmartIndent::handleDoxygen (KateDocCursor &begin) +{ + // Factor out the rather involved Doxygen stuff here ... + int line = begin.line(); + int first = -1; + while ((line > 0) && (first < 0)) + first = doc->plainKateTextLine(--line)->firstChar(); + + if (first >= 0) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + bool insideDoxygen = false; + bool justAfterDoxygen = false; + if (textLine->attribute(first) == doxyCommentAttrib || textLine->attribute(textLine->lastChar()) == doxyCommentAttrib) + { + const int last = textLine->lastChar(); + if (last <= 0 || !(justAfterDoxygen = textLine->stringAtPos(last-1, "*/"))) + insideDoxygen = true; + if (justAfterDoxygen) + justAfterDoxygen &= textLine->string().find("/**") < 0; + while (textLine->attribute(first) != doxyCommentAttrib && first <= textLine->lastChar()) + first++; + if (textLine->stringAtPos(first, "//")) + return false; + } + + // Align the *'s and then go ahead and insert one too ... + if (insideDoxygen) + { + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + int indent = findOpeningComment(begin); + QString filler = tabString (indent); + + bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; + + if ( doxygenAutoInsert && + ((first < 0) || (!textLine->stringAtPos(first, "*/") && !textLine->stringAtPos(first, "*")))) + { + filler = filler + " * "; + } + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + return true; + } + // Align position with beginning of doxygen comment. Otherwise the + // indentation is one too much. + else if (justAfterDoxygen) + { + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + int indent = findOpeningComment(begin); + QString filler = tabString (indent); + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + return true; + } + } + + return false; +} + +void KateCSmartIndent::processNewline (KateDocCursor &begin, bool needContinue) +{ + if (!handleDoxygen (begin)) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + bool inMiddle = textLine->firstChar() > -1; + + int indent = calcIndent (begin, needContinue); + + if (indent > 0 || inMiddle) + { + QString filler = tabString (indent); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + + // Handles cases where user hits enter at the beginning or middle of text + if (inMiddle) + { + processLine(begin); + begin.setCol(textLine->firstChar()); + } + } + else + { + KateNormalIndent::processNewline (begin, needContinue); + } + + if (begin.col() < 0) + begin.setCol(0); + } +} + +/** + * Returns true when the given attribute matches any "colon influence immune" + * attribute + * @param indenter indenter + * @param attr1 attribute of previous char + * @param attr2 attribute of char preceding previous char + * @param prev1 previous character (0 if none) + * @param prev2 character preceding previous character (0 if none) + */ +static inline bool isColonImmune(const KateNormalIndent &indenter, + uchar attr1, uchar attr2, + QChar prev1, QChar prev2) +{ + return attr1 == indenter.preprocessorAttrib + // FIXME: no way to discriminate against multiline comment and single + // line comment. Therefore, using prev? is futile. + || attr1 == indenter.commentAttrib /*&& prev2 != '*' && prev1 != '/'*/ + || attr1 == indenter.doxyCommentAttrib + || attr1 == indenter.stringAttrib && (attr2 != indenter.stringAttrib + || (prev1 != '"' || prev2 == '\\' && attr2 == indenter.charAttrib)) + || prev1 == '\'' && attr1 != indenter.charAttrib; +} + +/** + * Returns true when the colon is allowed to reindent the current line + * @param indenter current indenter + * @param line current line + * @param curCol column of most recently input character + */ +static inline bool colonPermitsReindent(const KateNormalIndent &indenter, + const KateTextLine::Ptr &line, + int curCol + ) +{ + const QString txt = line->string(0,curCol); + // do we have any significant preceding colon? + for (int pos = 0; (pos = txt.find(':', pos)) >= 0; pos++) { + if (line->attribute(pos) == indenter.symbolAttrib) + // yes, it has already contributed to this line's indentation, don't + // indent again + return false; + } + + // otherwise, check whether this colon is not within an influence + // immune attribute range + return !isColonImmune(indenter, line->attribute(curCol - 1), + line->attribute(curCol - 2), + txt[curCol - 1], txt[curCol - 2]); +} + +void KateCSmartIndent::processChar(QChar c) +{ + // You may be curious about 'n' among the triggers: + // It is used to discriminate C#'s #region/#endregion which are indented + // against normal preprocessing statements which aren't indented. + static const QString triggers("}{)/:#n"); + static const QString firstTriggers("}{)/:#"); + static const QString lastTriggers(":n"); + if (triggers.find(c) < 0) + return; + + KateView *view = doc->activeView(); + int curCol = view->cursorColumnReal() - 1; + KateDocCursor begin(view->cursorLine(), 0, doc); + + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + const QChar curChar = textLine->getChar(curCol); + const int first = textLine->firstChar(); + const QChar firstChar = textLine->getChar(first); + +#if 0 // nice try + // Only indent on symbols or preprocessing directives -- never on + // anything else + kdDebug() << "curChar " << curChar << " curCol " << curCol << " textlen " << textLine->length() << " a " << textLine->attribute( curCol ) << " sym " << symbolAttrib << " pp " << preprocessorAttrib << endl; + if (!(((curChar == '#' || curChar == 'n') + && textLine->attribute( curCol ) == preprocessorAttrib) + || textLine->attribute( curCol ) == symbolAttrib) + ) + return; + kdDebug() << "curChar " << curChar << endl; +#endif + + if (c == 'n') + { + if (firstChar != '#' || textLine->string(curCol-5, 5) != QString::fromLatin1("regio")) + return; + } + + if ( c == '/' ) + { + // dominik: if line is "* /", change it to "*/" + if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) + { + // if the first char exists and is a '*', and the next non-space-char + // is already the just typed '/', concatenate it to "*/". + if ( first != -1 + && firstChar == '*' + && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) + doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); + } + + // ls: never have comments change the indentation. + return; + } + + // ls: only reindent line if the user actually expects it + // I. e. take action on single braces on line or last colon, but inhibit + // any reindentation if any of those characters appear amidst some section + // of the line + const QChar lastChar = textLine->getChar(textLine->lastChar()); + int pos; + if (((c == firstChar && firstTriggers.find(firstChar) >= 0) + || (c == lastChar && lastTriggers.find(lastChar) >= 0)) + && (c != ':' || colonPermitsReindent(*this, textLine, curCol))) + processLine(begin); +} + + +uint KateCSmartIndent::calcIndent(KateDocCursor &begin, bool needContinue) +{ + KateTextLine::Ptr textLine; + KateDocCursor cur = begin; + + uint anchorIndent = 0; + int anchorPos = 0; + int parenCount = 0; // Possibly in a multiline for stmt. Used to skip ';' ... + bool found = false; + bool isSpecial = false; + bool potentialAnchorSeen = false; + bool isArg = false; // ...arg, + bool parenthesizedArg = false; // ...(arg, + + //kdDebug(13030) << "calcIndent begin line:" << begin.line() << " col:" << begin.col() << endl; + + // Find Indent Anchor Point + while (cur.gotoPreviousLine()) + { + isSpecial = found = false; + textLine = doc->plainKateTextLine(cur.line()); + + // Skip comments and handle cases like if (...) { stmt; + int pos = textLine->lastChar(); + int openCount = 0; + int otherAnchor = -1; + do + { + if (textLine->attribute(pos) == symbolAttrib) + { + QChar tc = textLine->getChar (pos); + if ((tc == ';' || tc == ':' || tc == ',') && otherAnchor == -1 && parenCount <= 0) { + otherAnchor = pos, potentialAnchorSeen = true; + isArg = tc == ','; + } else if (tc == ')') + parenCount++; + else if (tc == '(') + parenCount--, parenthesizedArg = isArg, potentialAnchorSeen = true; + else if (tc == '}') + openCount--; + else if (tc == '{') + { + openCount++, potentialAnchorSeen = true; + if (openCount == 1) + break; + } + } + } while (--pos >= textLine->firstChar()); + + if (openCount != 0 || otherAnchor != -1) + { + found = true; + QChar c; + if (openCount > 0) + c = '{'; + else if (openCount < 0) + c = '}'; + else if (otherAnchor >= 0) + c = textLine->getChar (otherAnchor); + + int specialIndent = 0; + if (c == ':' && needContinue) + { + QChar ch; + specialIndent = textLine->firstChar(); + if (textLine->stringAtPos(specialIndent, "case")) + ch = textLine->getChar(specialIndent + 4); + else if (textLine->stringAtPos(specialIndent, "default")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "public")) + ch = textLine->getChar(specialIndent + 6); + else if (textLine->stringAtPos(specialIndent, "private")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "protected")) + ch = textLine->getChar(specialIndent + 9); + else if (textLine->stringAtPos(specialIndent, "signals")) + ch = textLine->getChar(specialIndent + 7); + else if (textLine->stringAtPos(specialIndent, "Q_SIGNALS")) + ch = textLine->getChar(specialIndent + 9); + else if (textLine->stringAtPos(specialIndent, "slots")) + ch = textLine->getChar(specialIndent + 5); + else if (textLine->stringAtPos(specialIndent, "Q_SLOTS")) + ch = textLine->getChar(specialIndent + 7); + + if (ch.isNull() || (!ch.isSpace() && ch != '(' && ch != ':')) + continue; + + KateDocCursor lineBegin = cur; + lineBegin.setCol(specialIndent); + specialIndent = measureIndent(lineBegin); + isSpecial = true; + } + + // Move forward past blank lines + KateDocCursor skip = cur; + skip.setCol(textLine->lastChar()); + bool result = skipBlanks(skip, begin, true); + + anchorPos = skip.col(); + anchorIndent = measureIndent(skip); + + //kdDebug(13030) << "calcIndent anchorPos:" << anchorPos << " anchorIndent:" << anchorIndent << " at line:" << skip.line() << endl; + + // Accept if it's before requested position or if it was special + if (result && skip < begin) + { + cur = skip; + break; + } + else if (isSpecial) + { + anchorIndent = specialIndent; + break; + } + + // Are these on a line by themselves? (i.e. both last and first char) + if ((c == '{' || c == '}') && textLine->getChar(textLine->firstChar()) == c) + { + cur.setCol(anchorPos = textLine->firstChar()); + anchorIndent = measureIndent (cur); + break; + } + } + } + + // treat beginning of document as anchor position + if (cur.line() == 0 && cur.col() == 0 && potentialAnchorSeen) + found = true; + + if (!found) + return 0; + + uint continueIndent = (needContinue) ? calcContinue (cur, begin) : 0; + //kdDebug(13030) << "calcIndent continueIndent:" << continueIndent << endl; + + // Move forward from anchor and determine last known reference character + // Braces take precedance over others ... + textLine = doc->plainKateTextLine(cur.line()); + QChar lastChar = textLine->getChar (anchorPos); + int lastLine = cur.line(); + if (lastChar == '#' || lastChar == '[') + { + // Never continue if # or [ is encountered at this point here + // A fail-safe really... most likely an #include, #region, or a c# attribute + continueIndent = 0; + } + + int openCount = 0; + while (cur.validPosition() && cur < begin) + { + if (!skipBlanks(cur, begin, true)) + return isArg && !parenthesizedArg ? begin.col() : 0; + + QChar tc = cur.currentChar(); + //kdDebug(13030) << " cur.line:" << cur.line() << " cur.col:" << cur.col() << " currentChar '" << tc << "' " << textLine->attribute(cur.col()) << endl; + if (cur == begin || tc.isNull()) + break; + + if (!tc.isSpace() && cur < begin) + { + uchar attrib = cur.currentAttrib(); + if (tc == '{' && attrib == symbolAttrib) + openCount++; + else if (tc == '}' && attrib == symbolAttrib) + openCount--; + + lastChar = tc; + lastLine = cur.line(); + } + } + if (openCount > 0) // Open braces override + lastChar = '{'; + + uint indent = 0; + //kdDebug(13030) << "calcIndent lastChar '" << lastChar << "'" << endl; + + if (lastChar == '{' || (lastChar == ':' && isSpecial && needContinue)) + { + indent = anchorIndent + indentWidth; + } + else if (lastChar == '}') + { + indent = anchorIndent; + } + else if (lastChar == ';') + { + indent = anchorIndent + ((allowSemi && needContinue) ? continueIndent : 0); + } + else if (lastChar == ',' || lastChar == '(') + { + textLine = doc->plainKateTextLine(lastLine); + KateDocCursor start(lastLine, textLine->firstChar(), doc); + KateDocCursor finish(lastLine, textLine->lastChar() + 1, doc); + uint pos = 0; + + if (isBalanced(start, finish, QChar('('), QChar(')'), pos) && false) + indent = anchorIndent; + else + { + // TODO: Config option. If we're below 48, go ahead and line them up + indent = ((pos < 48) ? pos : anchorIndent + (indentWidth * 2)); + } + } + else if (!lastChar.isNull()) + { + if (anchorIndent != 0) + indent = anchorIndent + continueIndent; + else + indent = continueIndent; + } + + return indent; +} + +uint KateCSmartIndent::calcContinue(KateDocCursor &start, KateDocCursor &end) +{ + KateDocCursor cur = start; + + bool needsBalanced = true; + bool isFor = false; + allowSemi = false; + + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + // Handle cases such as } while (s ... by skipping the leading symbol + if (textLine->attribute(cur.col()) == symbolAttrib) + { + cur.moveForward(1); + skipBlanks(cur, end, false); + } + + if (textLine->getChar(cur.col()) == '}') + { + skipBlanks(cur, end, true); + if (cur.line() != start.line()) + textLine = doc->plainKateTextLine(cur.line()); + + if (textLine->stringAtPos(cur.col(), "else")) + cur.setCol(cur.col() + 4); + else + return indentWidth * 2; + + needsBalanced = false; + } + else if (textLine->stringAtPos(cur.col(), "else")) + { + cur.setCol(cur.col() + 4); + needsBalanced = false; + int next = textLine->nextNonSpaceChar(cur.col()); + if (next >= 0 && textLine->stringAtPos(next, "if")) + { + cur.setCol(next + 2); + needsBalanced = true; + } + } + else if (textLine->stringAtPos(cur.col(), "if")) + { + cur.setCol(cur.col() + 2); + } + else if (textLine->stringAtPos(cur.col(), "do")) + { + cur.setCol(cur.col() + 2); + needsBalanced = false; + } + else if (textLine->stringAtPos(cur.col(), "for")) + { + cur.setCol(cur.col() + 3); + isFor = true; + } + else if (textLine->stringAtPos(cur.col(), "while")) + { + cur.setCol(cur.col() + 5); + } + else if (textLine->stringAtPos(cur.col(), "switch")) + { + cur.setCol(cur.col() + 6); + } + else if (textLine->stringAtPos(cur.col(), "using")) + { + cur.setCol(cur.col() + 5); + } + else + { + return indentWidth * 2; + } + + uint openPos = 0; + if (needsBalanced && !isBalanced (cur, end, QChar('('), QChar(')'), openPos)) + { + allowSemi = isFor; + if (openPos > 0) + return (openPos - textLine->firstChar()); + else + return indentWidth * 2; + } + + // Check if this statement ends a line now + skipBlanks(cur, end, false); + if (cur == end) + return indentWidth; + + if (skipBlanks(cur, end, true)) + { + if (cur == end) + return indentWidth; + else + return indentWidth + calcContinue(cur, end); + } + + return 0; +} + +uint KateCSmartIndent::findOpeningBrace(KateDocCursor &start) +{ + KateDocCursor cur = start; + int count = 1; + + // Move backwards 1 by 1 and find the opening brace + // Return the indent of that line + while (cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '{') + count--; + else if (ch == '}') + count++; + + if (count == 0) + { + KateDocCursor temp(cur.line(), doc->plainKateTextLine(cur.line())->firstChar(), doc); + return measureIndent(temp); + } + } + } + + return 0; +} + +bool KateCSmartIndent::firstOpeningBrace(KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Are we the first opening brace at this level? + while(cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '{') + return false; + else if (ch == '}' && cur.col() == 0) + break; + } + } + + return true; +} + +uint KateCSmartIndent::findOpeningParen(KateDocCursor &start) +{ + KateDocCursor cur = start; + int count = 1; + + // Move backwards 1 by 1 and find the opening ( + // Return the indent of that line + while (cur.moveBackward(1)) + { + if (cur.currentAttrib() == symbolAttrib) + { + QChar ch = cur.currentChar(); + if (ch == '(') + count--; + else if (ch == ')') + count++; + + if (count == 0) + return measureIndent(cur); + } + } + + return 0; +} + +uint KateCSmartIndent::findOpeningComment(KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Find the line with the opening /* and return the proper indent + do + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + int pos = textLine->string().find("/*", false); + if (pos >= 0) + { + KateDocCursor temp(cur.line(), pos, doc); + return measureIndent(temp); + } + + } while (cur.gotoPreviousLine()); + + return 0; +} + +//END + +//BEGIN KatePythonIndent + +QRegExp KatePythonIndent::endWithColon = QRegExp( "^[^#]*:\\s*(#.*)?$" ); +QRegExp KatePythonIndent::stopStmt = QRegExp( "^\\s*(break|continue|raise|return|pass)\\b.*" ); +QRegExp KatePythonIndent::blockBegin = QRegExp( "^\\s*(class|def|if|elif|else|for|while|try)\\b.*" ); + +KatePythonIndent::KatePythonIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} +KatePythonIndent::~KatePythonIndent () +{ +} + +void KatePythonIndent::processNewline (KateDocCursor &begin, bool /*newline*/) +{ + int prevLine = begin.line() - 1; + int prevPos = begin.col(); + + while ((prevLine > 0) && (prevPos < 0)) // search a not empty text line + prevPos = doc->plainKateTextLine(--prevLine)->firstChar(); + + int prevBlock = prevLine; + int prevBlockPos = prevPos; + int extraIndent = calcExtra (prevBlock, prevBlockPos, begin); + + int indent = doc->plainKateTextLine(prevBlock)->cursorX(prevBlockPos, tabWidth); + if (extraIndent == 0) + { + if (!stopStmt.exactMatch(doc->plainKateTextLine(prevLine)->string())) + { + if (endWithColon.exactMatch(doc->plainKateTextLine(prevLine)->string())) + indent += indentWidth; + else + indent = doc->plainKateTextLine(prevLine)->cursorX(prevPos, tabWidth); + } + } + else + indent += extraIndent; + + if (indent > 0) + { + QString filler = tabString (indent); + doc->insertText (begin.line(), 0, filler); + begin.setCol(filler.length()); + } + else + begin.setCol(0); +} + +int KatePythonIndent::calcExtra (int &prevBlock, int &pos, KateDocCursor &end) +{ + int nestLevel = 0; + bool levelFound = false; + while ((prevBlock > 0)) + { + if (blockBegin.exactMatch(doc->plainKateTextLine(prevBlock)->string())) + { + if ((!levelFound && nestLevel == 0) || (levelFound && nestLevel - 1 <= 0)) + { + pos = doc->plainKateTextLine(prevBlock)->firstChar(); + break; + } + + nestLevel --; + } + else if (stopStmt.exactMatch(doc->plainKateTextLine(prevBlock)->string())) + { + nestLevel ++; + levelFound = true; + } + + --prevBlock; + } + + KateDocCursor cur (prevBlock, pos, doc); + QChar c; + int extraIndent = 0; + while (cur.line() < end.line()) + { + c = cur.currentChar(); + + if (c == '(') + extraIndent += indentWidth; + else if (c == ')') + extraIndent -= indentWidth; + else if (c == ':') + break; + else if (c == '\'' || c == '"' ) + traverseString( c, cur, end ); + + if (c.isNull() || c == '#') + cur.gotoNextLine(); + else + cur.moveForward(1); + } + + return extraIndent; +} + +void KatePythonIndent::traverseString( const QChar &stringChar, KateDocCursor &cur, KateDocCursor &end ) +{ + QChar c; + bool escape = false; + + cur.moveForward(1); + c = cur.currentChar(); + while ( ( c != stringChar || escape ) && cur.line() < end.line() ) + { + if ( escape ) + escape = false; + else if ( c == '\\' ) + escape = !escape; + + cur.moveForward(1); + c = cur.currentChar(); + } +} + +//END + +//BEGIN KateXmlIndent + +/* Explanation + +The XML indenter simply inherits the indentation of the previous line, +with the first line starting at 0 (of course!). For each element that +is opened on the previous line, the indentation is increased by one +level; for each element that is closed, it is decreased by one. + +We also have a special case of opening an element on one line and then +entering attributes on the following lines, in which case we would like +to see the following layout: + + + + + +This is accomplished by checking for lines that contain an unclosed open +tag. + +*/ + +const QRegExp KateXmlIndent::startsWithCloseTag("^[ \t]*]*$"); + +KateXmlIndent::KateXmlIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} + +KateXmlIndent::~KateXmlIndent () +{ +} + +void KateXmlIndent::processNewline (KateDocCursor &begin, bool /*newline*/) +{ + begin.setCol(processLine(begin.line())); +} + +void KateXmlIndent::processChar (QChar c) +{ + if(c != '/') return; + + // only alter lines that start with a close element + KateView *view = doc->activeView(); + QString text = doc->plainKateTextLine(view->cursorLine())->string(); + if(text.find(startsWithCloseTag) == -1) return; + + // process it + processLine(view->cursorLine()); +} + +void KateXmlIndent::processLine (KateDocCursor &line) +{ + processLine (line.line()); +} + +void KateXmlIndent::processSection (const KateDocCursor &start, const KateDocCursor &end) +{ + KateDocCursor cur (start); + int endLine = end.line(); + + do { + processLine(cur.line()); + if(!cur.gotoNextLine()) break; + } while(cur.line() < endLine); +} + +void KateXmlIndent::getLineInfo (uint line, uint &prevIndent, int &numTags, + uint &attrCol, bool &unclosedTag) +{ + prevIndent = 0; + int firstChar; + KateTextLine::Ptr prevLine = 0; + + // get the indentation of the first non-empty line + while(true) { + prevLine = doc->plainKateTextLine(line); + if( (firstChar = prevLine->firstChar()) < 0) { + if(!line--) return; + continue; + } + break; + } + prevIndent = prevLine->cursorX(prevLine->firstChar(), tabWidth); + QString text = prevLine->string(); + + // special case: + // + // + // requires that we discount the from the number of closed tags + if(text.find(startsWithCloseTag) != -1) ++numTags; + + // count the number of open and close tags + int lastCh = 0; + uint pos, len = text.length(); + bool seenOpen = false; + for(pos = 0; pos < len; ++pos) { + int ch = text.at(pos).unicode(); + switch(ch) { + case '<': + seenOpen = true; + unclosedTag = true; + attrCol = pos; + ++numTags; + break; + + // don't indent because of DOCTYPE, comment, CDATA, etc. + case '!': + if(lastCh == '<') --numTags; + break; + + // don't indent because of xml decl or PI + case '?': + if(lastCh == '<') --numTags; + break; + + case '>': + if(!seenOpen) { + // we are on a line like the second one here: + // + // so we need to set prevIndent to the indent of the first line + // + // however, we need to special case "plainKateTextLine(--backLine); + if(x->string().find('<') == -1) continue; + + // recalculate the indent + if(x->string().find(unclosedDoctype) != -1) --numTags; + getLineInfo(backLine, prevIndent, numTags, attrCol, unclosedTag); + break; + } + } + if(lastCh == '/') --numTags; + unclosedTag = false; + break; + + case '/': + if(lastCh == '<') numTags -= 2; // correct for '<', above + break; + } + lastCh = ch; + } + + if(unclosedTag) { + // find the start of the next attribute, so we can align with it + do { + lastCh = text.at(++attrCol).unicode(); + }while(lastCh && lastCh != ' ' && lastCh != '\t'); + + while(lastCh == ' ' || lastCh == '\t') { + lastCh = text.at(++attrCol).unicode(); + } + + attrCol = prevLine->cursorX(attrCol, tabWidth); + } +} + +uint KateXmlIndent::processLine (uint line) +{ + KateTextLine::Ptr kateLine = doc->plainKateTextLine(line); + if(!kateLine) return 0; // sanity check + + // get details from previous line + uint prevIndent = 0, attrCol = 0; + int numTags = 0; + bool unclosedTag = false; // for aligning attributes + + if(line) { + getLineInfo(line - 1, prevIndent, numTags, attrCol, unclosedTag); + } + + // compute new indent + int indent = 0; + if(unclosedTag) indent = attrCol; + else indent = prevIndent + numTags * indentWidth; + if(indent < 0) indent = 0; + + // unindent lines that start with a close tag + if(kateLine->string().find(startsWithCloseTag) != -1) { + indent -= indentWidth; + } + if(indent < 0) indent = 0; + + // apply new indent + doc->removeText(line, 0, line, kateLine->firstChar()); + QString filler = tabString(indent); + doc->insertText(line, 0, filler); + + return filler.length(); +} + +//END + +//BEGIN KateCSAndSIndent + +KateCSAndSIndent::KateCSAndSIndent (KateDocument *doc) +: KateNormalIndent (doc) +{ +} + +void KateCSAndSIndent::updateIndentString() +{ + if( useSpaces ) + indentString.fill( ' ', indentWidth ); + else + indentString = '\t'; +} + +KateCSAndSIndent::~KateCSAndSIndent () +{ +} + +void KateCSAndSIndent::processLine (KateDocCursor &line) +{ + KateTextLine::Ptr textLine = doc->plainKateTextLine(line.line()); + + if (!textLine) + return; + + updateIndentString(); + + const int oldCol = line.col(); + QString whitespace = calcIndent(line); + // strip off existing whitespace + int oldIndent = textLine->firstChar(); + if ( oldIndent < 0 ) + oldIndent = doc->lineLength( line.line() ); + if( oldIndent > 0 ) + doc->removeText(line.line(), 0, line.line(), oldIndent); + // add correct amount + doc->insertText(line.line(), 0, whitespace); + + // try to preserve the cursor position in the line + if ( int(oldCol + whitespace.length()) >= oldIndent ) + line.setCol( oldCol + whitespace.length() - oldIndent ); + else + line.setCol( 0 ); +} + +void KateCSAndSIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + QTime t; t.start(); + for( KateDocCursor cur = begin; cur.line() <= end.line(); ) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } + kdDebug(13030) << "+++ total: " << t.elapsed() << endl; +} + +/** + * Returns the first @p chars characters of @p line, converted to whitespace. + * If @p convert is set to false, characters at and after the first non-whitespace + * character are removed, not converted. + */ +static QString initialWhitespace(const KateTextLine::Ptr &line, int chars, bool convert = true) +{ + QString text = line->string(0, chars); + if( (int)text.length() < chars ) + { + QString filler; filler.fill(' ',chars - text.length()); + text += filler; + } + for( uint n = 0; n < text.length(); ++n ) + { + if( text[n] != '\t' && text[n] != ' ' ) + { + if( !convert ) + return text.left( n ); + text[n] = ' '; + } + } + return text; +} + +QString KateCSAndSIndent::findOpeningCommentIndentation(const KateDocCursor &start) +{ + KateDocCursor cur = start; + + // Find the line with the opening /* and return the indentation of it + do + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + + int pos = textLine->string().findRev("/*"); + // FIXME: /* inside /* is possible. This screws up in that case... + if (pos >= 0) + return initialWhitespace(textLine, pos); + } while (cur.gotoPreviousLine()); + + // should never happen. + kdWarning( 13030 ) << " in a comment, but can't find the start of it" << endl; + return QString::null; +} + +bool KateCSAndSIndent::handleDoxygen (KateDocCursor &begin) +{ + // Look backwards for a nonempty line + int line = begin.line(); + int first = -1; + while ((line > 0) && (first < 0)) + first = doc->plainKateTextLine(--line)->firstChar(); + + // no earlier nonempty line + if (first < 0) + return false; + + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + + // if the line doesn't end with a doxygen comment (that's not closed) + // and doesn't start with a doxygen comment (that's not closed), we don't care. + // note that we do need to check the start of the line, or lines ending with, say, @brief aren't + // recognised. + if ( !(textLine->attribute(textLine->lastChar()) == doxyCommentAttrib && !textLine->endingWith("*/")) && + !(textLine->attribute(textLine->firstChar()) == doxyCommentAttrib && !textLine->string().contains("*/")) ) + return false; + + // our line is inside a doxygen comment. align the *'s and then maybe insert one too ... + textLine = doc->plainKateTextLine(begin.line()); + first = textLine->firstChar(); + QString indent = findOpeningCommentIndentation(begin); + + bool doxygenAutoInsert = doc->config()->configFlags() & KateDocumentConfig::cfDoxygenAutoTyping; + + // starts with *: indent one space more to line up *s + if ( first >= 0 && textLine->stringAtPos(first, "*") ) + indent = indent + " "; + // does not start with *: insert one if user wants that + else if ( doxygenAutoInsert ) + indent = indent + " * "; + // user doesn't want * inserted automatically: put in spaces? + //else + // indent = indent + " "; + + doc->removeText (begin.line(), 0, begin.line(), first); + doc->insertText (begin.line(), 0, indent); + begin.setCol(indent.length()); + + return true; +} + +/** + * @brief User pressed enter. Line has been split; begin is on the new line. + * @param begin Three unrelated variables: the new line number, where the first + * non-whitespace char was on the previous line, and the document. + * @param needContinue Something to do with indenting the current line; always true. + */ +void KateCSAndSIndent::processNewline (KateDocCursor &begin, bool /*needContinue*/) +{ + // in a comment, add a * doxygen-style. + if( handleDoxygen(begin) ) + return; + + // TODO: if the user presses enter in the middle of a label, maybe the first half of the + // label should be indented? + + // where the cursor actually is... + int cursorPos = doc->plainKateTextLine( begin.line() )->firstChar(); + if ( cursorPos < 0 ) + cursorPos = doc->lineLength( begin.line() ); + begin.setCol( cursorPos ); + + processLine( begin ); +} + +/** + * Does the line @p line start with a label? + * @note May also return @c true if the line starts in a continuation. + */ +bool KateCSAndSIndent::startsWithLabel( int line ) +{ + // Get the current line. + KateTextLine::Ptr indentLine = doc->plainKateTextLine(line); + const int indentFirst = indentLine->firstChar(); + + // Not entirely sure what this check does. + int attrib = indentLine->attribute(indentFirst); + if (attrib != 0 && attrib != keywordAttrib && attrib != normalAttrib && attrib != extensionAttrib) + return false; + + // Get the line text. + const QString lineContents = indentLine->string(); + const int indentLast = indentLine->lastChar(); + bool whitespaceFound = false; + for ( int n = indentFirst; n <= indentLast; ++n ) + { + // Get the character as latin1. Can't use QChar::isLetterOrNumber() + // as that includes non 0-9 numbers. + char c = lineContents[n].latin1(); + if ( c == ':' ) + { + // See if the next character is ':' - if so, skip to the character after it. + if ( n < lineContents.length() - 1 ) + { + if ( lineContents[n+1].latin1() == ':' ) + { + n += 2; + continue; + } + } + // Right this is the relevent ':'. + if ( n == indentFirst) + { + // Just a line with a : on it. + return false; + } + // It is a label of some kind! + return true; + } + if (isspace(c)) + { + if (!whitespaceFound) + { + if (lineContents.mid(indentFirst, n - indentFirst) == "case") + return true; + else if (lineContents.mid(indentFirst, n - indentFirst) == "class") + return false; + whitespaceFound = true; + } + } + // All other characters don't indent. + else if ( !isalnum(c) && c != '_' ) + { + return false; + } + } + return false; +} + +template T min(T a, T b) { return (a < b) ? a : b; } + +int KateCSAndSIndent::lastNonCommentChar( const KateDocCursor &line ) +{ + KateTextLine::Ptr textLine = doc->plainKateTextLine( line.line() ); + QString str = textLine->string(); + + // find a possible start-of-comment + int p = -2; // so the first find starts at position 0 + do p = str.find( "//", p + 2 ); + while ( p >= 0 && textLine->attribute(p) != commentAttrib && textLine->attribute(p) != doxyCommentAttrib ); + + // no // found? use whole string + if ( p < 0 ) + p = str.length(); + + // ignore trailing blanks. p starts one-past-the-end. + while( p > 0 && str[p-1].isSpace() ) --p; + return p - 1; +} + +bool KateCSAndSIndent::inForStatement( int line ) +{ + // does this line end in a for ( ... + // with no closing ) ? + int parens = 0, semicolons = 0; + for ( ; line >= 0; --line ) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(line); + const int first = textLine->firstChar(); + const int last = textLine->lastChar(); + + // look backwards for a symbol: (){}; + // match ()s, {...; and }...; => not in a for + // ; ; ; => not in a for + // ( ; and ( ; ; => a for + for ( int curr = last; curr >= first; --curr ) + { + if ( textLine->attribute(curr) != symbolAttrib ) + continue; + + switch( textLine->getChar(curr) ) + { + case ';': + if( ++semicolons > 2 ) + return false; + break; + case '{': case '}': + return false; + case ')': + ++parens; + break; + case '(': + if( --parens < 0 ) + return true; + break; + } + } + } + // no useful symbols before the ;? + // not in a for then + return false; +} + + +// is the start of the line containing 'begin' in a statement? +bool KateCSAndSIndent::inStatement( const KateDocCursor &begin ) +{ + // if the current line starts with an open brace, it's not a continuation. + // this happens after a function definition (which is treated as a continuation). + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + const int first = textLine->firstChar(); + // note that if we're being called from processChar the attribute has not yet been calculated + // should be reasonably safe to assume that unattributed {s are symbols; if the { is in a comment + // we don't want to touch it anyway. + const int attrib = textLine->attribute(first); + if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && textLine->getChar(first) == '{' ) + return false; + + int line; + for ( line = begin.line() - 1; line >= 0; --line ) + { + textLine = doc->plainKateTextLine(line); + const int first = textLine->firstChar(); + if ( first == -1 ) + continue; + + // starts with #: in a comment, don't care + // outside a comment: preprocessor, don't care + if ( textLine->getChar( first ) == '#' ) + continue; + KateDocCursor currLine = begin; + currLine.setLine( line ); + const int last = lastNonCommentChar( currLine ); + if ( last < first ) + continue; + + // HACK: if we see a comment, assume boldly that this isn't a continuation. + // detecting comments (using attributes) is HARD, since they may have + // embedded alerts, or doxygen stuff, or just about anything. this is + // wrong, and needs fixing. note that only multi-line comments and + // single-line comments continued with \ are affected. + const int attrib = textLine->attribute(last); + if ( attrib == commentAttrib || attrib == doxyCommentAttrib ) + return false; + + char c = textLine->getChar(last); + + // brace => not a continuation. + if ( attrib == symbolAttrib && c == '{' || c == '}' ) + return false; + + // ; => not a continuation, unless in a for (;;) + if ( attrib == symbolAttrib && c == ';' ) + return inForStatement( line ); + + // found something interesting. maybe it's a label? + if ( attrib == symbolAttrib && c == ':' ) + { + // the : above isn't necessarily the : in the label, eg in + // case 'x': a = b ? c : + // this will say no continuation incorrectly. but continued statements + // starting on a line with a label at the start is Bad Style (tm). + if( startsWithLabel( line ) ) + { + // either starts with a label or a continuation. if the current line + // starts in a continuation, we're still in one. if not, this was + // a label, so we're not in one now. so continue to the next line + // upwards. + continue; + } + } + + // any other character => in a continuation + return true; + } + // no non-comment text found before here - not a continuation. + return false; +} + +QString KateCSAndSIndent::continuationIndent( const KateDocCursor &begin ) +{ + if( !inStatement( begin ) ) + return QString::null; + return indentString; +} + +/** + * Figure out how indented the line containing @p begin should be. + */ +QString KateCSAndSIndent::calcIndent (const KateDocCursor &begin) +{ + KateTextLine::Ptr currLine = doc->plainKateTextLine(begin.line()); + int currLineFirst = currLine->firstChar(); + + // if the line starts inside a comment, no change of indentation. + // FIXME: this unnecessarily copies the current indentation over itself. + // FIXME: on newline, this should copy from the previous line. + if ( currLineFirst >= 0 && + (currLine->attribute(currLineFirst) == commentAttrib || + currLine->attribute(currLineFirst) == doxyCommentAttrib) ) + return currLine->string( 0, currLineFirst ); + + // if the line starts with # (but isn't a c# region thingy), no indentation at all. + if( currLineFirst >= 0 && currLine->getChar(currLineFirst) == '#' ) + { + if( !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("region") ) && + !currLine->stringAtPos( currLineFirst+1, QString::fromLatin1("endregion") ) ) + return QString::null; + } + + /* Strategy: + * Look for an open bracket or brace, or a keyword opening a new scope, whichever comes latest. + * Found a brace: indent one tab in. + * Found a bracket: indent to the first non-white after it. + * Found a keyword: indent one tab in. for try, catch and switch, if newline is set, also add + * an open brace, a newline, and indent two tabs in. + */ + KateDocCursor cur = begin; + int pos, openBraceCount = 0, openParenCount = 0; + bool lookingForScopeKeywords = true; + const char * const scopeKeywords[] = { "for", "do", "while", "if", "else" }; + const char * const blockScopeKeywords[] = { "try", "catch", "switch" }; + + while (cur.gotoPreviousLine()) + { + KateTextLine::Ptr textLine = doc->plainKateTextLine(cur.line()); + const int lastChar = textLine->lastChar(); + const int firstChar = textLine->firstChar(); + + // look through line backwards for interesting characters + for( pos = lastChar; pos >= firstChar; --pos ) + { + if (textLine->attribute(pos) == symbolAttrib) + { + char tc = textLine->getChar (pos); + switch( tc ) + { + case '(': case '[': + if( ++openParenCount > 0 ) + return calcIndentInBracket( begin, cur, pos ); + break; + case ')': case ']': openParenCount--; break; + case '{': + if( ++openBraceCount > 0 ) + return calcIndentInBrace( begin, cur, pos ); + break; + case '}': openBraceCount--; lookingForScopeKeywords = false; break; + case ';': + if( openParenCount == 0 ) + lookingForScopeKeywords = false; + break; + } + } + + // if we've not had a close brace or a semicolon yet, and we're at the same parenthesis level + // as the cursor, and we're at the start of a scope keyword, indent from it. + if ( lookingForScopeKeywords && openParenCount == 0 && + textLine->attribute(pos) == keywordAttrib && + (pos == 0 || textLine->attribute(pos-1) != keywordAttrib ) ) + { + #define ARRLEN( array ) ( sizeof(array)/sizeof(array[0]) ) + for( uint n = 0; n < ARRLEN(scopeKeywords); ++n ) + if( textLine->stringAtPos(pos, QString::fromLatin1(scopeKeywords[n]) ) ) + return calcIndentAfterKeyword( begin, cur, pos, false ); + for( uint n = 0; n < ARRLEN(blockScopeKeywords); ++n ) + if( textLine->stringAtPos(pos, QString::fromLatin1(blockScopeKeywords[n]) ) ) + return calcIndentAfterKeyword( begin, cur, pos, true ); + #undef ARRLEN + } + } + } + + // no active { in file. + return QString::null; +} + +QString KateCSAndSIndent::calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos) +{ + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + KateTextLine::Ptr bracketLine = doc->plainKateTextLine(bracketCursor.line()); + + // FIXME: hard-coded max indent to bracket width - use a kate variable + // FIXME: expand tabs first... + if ( bracketPos > 48 ) + { + // how far to indent? we could look back for a brace or keyword, 2 from that. + // as it is, we just indent one more than the line with the ( on it. + // the potential problem with this is when + // you have code ( which does <-- continuation + start of func call + // something like this ); <-- extra indentation for func call + // then again ( + // it works better than ( + // the other method for ( + // cases like this ))); + // consequently, i think this method wins. + return indentString + initialWhitespace( bracketLine, bracketLine->firstChar() ); + } + + const int indentLineFirst = indentLine->firstChar(); + + int indentTo; + const int attrib = indentLine->attribute(indentLineFirst); + if( indentLineFirst >= 0 && (attrib == 0 || attrib == symbolAttrib) && + ( indentLine->getChar(indentLineFirst) == ')' || indentLine->getChar(indentLineFirst) == ']' ) ) + { + // If the line starts with a close bracket, line it up + indentTo = bracketPos; + } + else + { + // Otherwise, line up with the text after the open bracket + indentTo = bracketLine->nextNonSpaceChar( bracketPos + 1 ); + if( indentTo == -1 ) + indentTo = bracketPos + 2; + } + return initialWhitespace( bracketLine, indentTo ); +} + +QString KateCSAndSIndent::calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword) +{ + KateTextLine::Ptr keywordLine = doc->plainKateTextLine(keywordCursor.line()); + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + + QString whitespaceToKeyword = initialWhitespace( keywordLine, keywordPos, false ); + if( blockKeyword ) { + // FIXME: we could add the open brace and subsequent newline here since they're definitely needed. + } + + // If the line starts with an open brace, don't indent... + int first = indentLine->firstChar(); + // if we're being called from processChar attribute won't be set + const int attrib = indentLine->attribute(first); + if( first >= 0 && (attrib == 0 || attrib == symbolAttrib) && indentLine->getChar(first) == '{' ) + return whitespaceToKeyword; + + // don't check for a continuation. rules are simple here: + // if we're in a non-compound statement after a scope keyword, we indent all lines + // once. so: + // if ( some stuff + // goes here ) + // apples, and <-- continuation here is ignored. but this is Bad Style (tm) anyway. + // oranges too; + return indentString + whitespaceToKeyword; +} + +QString KateCSAndSIndent::calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos) +{ + KateTextLine::Ptr braceLine = doc->plainKateTextLine(braceCursor.line()); + const int braceFirst = braceLine->firstChar(); + + QString whitespaceToOpenBrace = initialWhitespace( braceLine, bracePos, false ); + + // if the open brace is the start of a namespace, don't indent... + // FIXME: this is an extremely poor heuristic. it looks on the line with + // the { and the line before to see if they start with a keyword + // beginning 'namespace'. that's 99% of usage, I'd guess. + { + if( braceFirst >= 0 && braceLine->attribute(braceFirst) == keywordAttrib && + braceLine->stringAtPos( braceFirst, QString::fromLatin1( "namespace" ) ) ) + return continuationIndent(indentCursor) + whitespaceToOpenBrace; + + if( braceCursor.line() > 0 ) + { + KateTextLine::Ptr prevLine = doc->plainKateTextLine(braceCursor.line() - 1); + int firstPrev = prevLine->firstChar(); + if( firstPrev >= 0 && prevLine->attribute(firstPrev) == keywordAttrib && + prevLine->stringAtPos( firstPrev, QString::fromLatin1( "namespace" ) ) ) + return continuationIndent(indentCursor) + whitespaceToOpenBrace; + } + } + + KateTextLine::Ptr indentLine = doc->plainKateTextLine(indentCursor.line()); + const int indentFirst = indentLine->firstChar(); + + // if the line starts with a close brace, don't indent... + if( indentFirst >= 0 && indentLine->getChar(indentFirst) == '}' ) + return whitespaceToOpenBrace; + + // if : is the first character (and not followed by another :), this is the start + // of an initialization list, or a continuation of a ?:. either way, indent twice. + if ( indentFirst >= 0 && indentLine->attribute(indentFirst) == symbolAttrib && + indentLine->getChar(indentFirst) == ':' && indentLine->getChar(indentFirst+1) != ':' ) + { + return indentString + indentString + whitespaceToOpenBrace; + } + + const bool continuation = inStatement(indentCursor); + // if the current line starts with a label, don't indent... + if( !continuation && startsWithLabel( indentCursor.line() ) ) + return whitespaceToOpenBrace; + + // the normal case: indent once for the brace, again if it's a continuation + QString continuationIndent = continuation ? indentString : QString::null; + return indentString + continuationIndent + whitespaceToOpenBrace; +} + +void KateCSAndSIndent::processChar(QChar c) +{ + // 'n' trigger is for c# regions. + static const QString triggers("}{)]/:;#n"); + if (triggers.find(c) == -1) + return; + + // for historic reasons, processChar doesn't get a cursor + // to work on. so fabricate one. + KateView *view = doc->activeView(); + KateDocCursor begin(view->cursorLine(), 0, doc); + + KateTextLine::Ptr textLine = doc->plainKateTextLine(begin.line()); + if ( c == 'n' ) + { + int first = textLine->firstChar(); + if( first < 0 || textLine->getChar(first) != '#' ) + return; + } + + if ( textLine->attribute( begin.col() ) == doxyCommentAttrib ) + { + // dominik: if line is "* /", change it to "*/" + if ( c == '/' ) + { + int first = textLine->firstChar(); + // if the first char exists and is a '*', and the next non-space-char + // is already the just typed '/', concatenate it to "*/". + if ( first != -1 + && textLine->getChar( first ) == '*' + && textLine->nextNonSpaceChar( first+1 ) == view->cursorColumnReal()-1 ) + doc->removeText( view->cursorLine(), first+1, view->cursorLine(), view->cursorColumnReal()-1); + } + + // anders: don't change the indent of doxygen lines here. + return; + } + + processLine(begin); +} + +//END + +//BEGIN KateVarIndent +class KateVarIndentPrivate { + public: + QRegExp reIndentAfter, reIndent, reUnindent; + QString triggers; + uint couples; + uchar coupleAttrib; +}; + +KateVarIndent::KateVarIndent( KateDocument *doc ) +: KateNormalIndent( doc ) +{ + d = new KateVarIndentPrivate; + d->reIndentAfter = QRegExp( doc->variable( "var-indent-indent-after" ) ); + d->reIndent = QRegExp( doc->variable( "var-indent-indent" ) ); + d->reUnindent = QRegExp( doc->variable( "var-indent-unindent" ) ); + d->triggers = doc->variable( "var-indent-triggerchars" ); + d->coupleAttrib = 0; + + slotVariableChanged( "var-indent-couple-attribute", doc->variable( "var-indent-couple-attribute" ) ); + slotVariableChanged( "var-indent-handle-couples", doc->variable( "var-indent-handle-couples" ) ); + + // update if a setting is changed + connect( doc, SIGNAL(variableChanged( const QString&, const QString&) ), + this, SLOT(slotVariableChanged( const QString&, const QString& )) ); +} + +KateVarIndent::~KateVarIndent() +{ + delete d; +} + +void KateVarIndent::processNewline ( KateDocCursor &begin, bool /*needContinue*/ ) +{ + // process the line left, as well as the one entered + KateDocCursor left( begin.line()-1, 0, doc ); + processLine( left ); + processLine( begin ); +} + +void KateVarIndent::processChar ( QChar c ) +{ + // process line if the c is in our list, and we are not in comment text + if ( d->triggers.contains( c ) ) + { + KateTextLine::Ptr ln = doc->plainKateTextLine( doc->activeView()->cursorLine() ); + if ( ln->attribute( doc->activeView()->cursorColumn()-1 ) == commentAttrib ) + return; + + KateView *view = doc->activeView(); + KateDocCursor begin( view->cursorLine(), 0, doc ); + kdDebug(13030)<<"variable indenter: process char '"<plainKateTextLine( ln ); + if ( ! ktl ) return; // no line!? + + // skip blank lines, except for the cursor line + KateView *v = doc->activeView(); + if ( (ktl->firstChar() < 0) && (!v || (int)v->cursorLine() != ln ) ) + return; + + int fc; + if ( ln > 0 ) + do + { + + ktl = doc->plainKateTextLine( --ln ); + fc = ktl->firstChar(); + if ( ktl->attribute( fc ) != commentAttrib ) + pos = fc; + } + while ( (ln > 0) && (pos < 0) ); // search a not empty text line + + if ( pos < 0 ) + pos = 0; + else + pos = ktl->cursorX( pos, tabWidth ); + + int adjustment = 0; + + // try 'couples' for an opening on the above line first. since we only adjust by 1 unit, + // we only need 1 match. + if ( d->couples & Parens && coupleBalance( ln, '(', ')' ) > 0 ) + adjustment++; + else if ( d->couples & Braces && coupleBalance( ln, '{', '}' ) > 0 ) + adjustment++; + else if ( d->couples & Brackets && coupleBalance( ln, '[', ']' ) > 0 ) + adjustment++; + + // Try 'couples' for a closing on this line first. since we only adjust by 1 unit, + // we only need 1 match. For unindenting, we look for a closing character + // *at the beginning of the line* + // NOTE Assume that a closing brace with the configured attribute on the start + // of the line is closing. + // When acting on processChar, the character isn't highlighted. So I could + // either not check, assuming that the first char *is* meant to close, or do a + // match test if the attrib is 0. How ever, doing that is + // a potentially huge job, if the match is several hundred lines away. + // Currently, the check is done. + { + KateTextLine::Ptr tl = doc->plainKateTextLine( line.line() ); + int i = tl->firstChar(); + if ( i > -1 ) + { + QChar ch = tl->getChar( i ); + uchar at = tl->attribute( i ); + kdDebug(13030)<<"attrib is "<couples & Parens && ch == ')' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + else if ( d->couples & Braces && ch == '}' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + else if ( d->couples & Brackets && ch == ']' + && ( at == d->coupleAttrib + || (! at && hasRelevantOpening( KateDocCursor( line.line(), i, doc ) )) + ) + ) + adjustment--; + } + } +#define ISCOMMENTATTR(attr) (attr==commentAttrib||attr==doxyCommentAttrib) +#define ISCOMMENT (ISCOMMENTATTR(ktl->attribute(ktl->firstChar()))||ISCOMMENTATTR(ktl->attribute(matchpos))) + // check if we should indent, unless the line starts with comment text, + // or the match is in comment text + kdDebug(13030)<<"variable indenter: starting indent: "<reIndentAfter.isEmpty() + && (matchpos = d->reIndentAfter.search( doc->textLine( ln ) )) > -1 + && ! ISCOMMENT ) + adjustment++; + + // else, check if this line should indent unless ... + ktl = doc->plainKateTextLine( line.line() ); + if ( ! d->reIndent.isEmpty() + && (matchpos = d->reIndent.search( doc->textLine( line.line() ) )) > -1 + && ! ISCOMMENT ) + adjustment++; + + // else, check if the current line indicates if we should remove indentation unless ... + if ( ! d->reUnindent.isEmpty() + && (matchpos = d->reUnindent.search( doc->textLine( line.line() ) )) > -1 + && ! ISCOMMENT ) + adjustment--; + + kdDebug(13030)<<"variable indenter: adjusting by "< 0 ) + pos += indentWidth; + else if ( adjustment < 0 ) + pos -= indentWidth; + + ln = line.line(); + fc = doc->plainKateTextLine( ln )->firstChar(); + + // dont change if there is no change. + // ### should I actually compare the strings? + // FIXME for some odd reason, the document gets marked as changed + // even if we don't change it !? + if ( fc == pos ) + return; + + if ( fc > 0 ) + doc->removeText (ln, 0, ln, fc ); + + if ( pos > 0 ) + indent = tabString( pos ); + + if ( pos > 0 ) + doc->insertText (ln, 0, indent); + + // try to restore cursor ? + line.setCol( pos ); +} + +void KateVarIndent::processSection (const KateDocCursor &begin, const KateDocCursor &end) +{ + KateDocCursor cur = begin; + while (cur.line() <= end.line()) + { + processLine (cur); + if (!cur.gotoNextLine()) + break; + } +} + +void KateVarIndent::slotVariableChanged( const QString &var, const QString &val ) +{ + if ( ! var.startsWith("var-indent") ) + return; + + if ( var == "var-indent-indent-after" ) + d->reIndentAfter.setPattern( val ); + else if ( var == "var-indent-indent" ) + d->reIndent.setPattern( val ); + else if ( var == "var-indent-unindent" ) + d->reUnindent.setPattern( val ); + else if ( var == "var-indent-triggerchars" ) + d->triggers = val; + else if ( var == "var-indent-handle-couples" ) + { + d->couples = 0; + QStringList l = QStringList::split( " ", val ); + if ( l.contains("parens") ) d->couples |= Parens; + if ( l.contains("braces") ) d->couples |= Braces; + if ( l.contains("brackets") ) d->couples |= Brackets; + } + else if ( var == "var-indent-couple-attribute" ) + { + //read a named attribute of the config. + KateHlItemDataList items; + doc->highlight()->getKateHlItemDataListCopy (0, items); + + for (uint i=0; iname.section( ':', 1 ) == val ) + { + d->coupleAttrib = i; + break; + } + } + } +} + +int KateVarIndent::coupleBalance ( int line, const QChar &open, const QChar &close ) const +{ + int r = 0; + + KateTextLine::Ptr ln = doc->plainKateTextLine( line ); + if ( ! ln || ! ln->length() ) return 0; + + for ( uint z=0; z < ln->length(); z++ ) + { + QChar c = ln->getChar( z ); + if ( ln->attribute(z) == d->coupleAttrib ) + { + kdDebug(13030)<coupleAttrib) + { + QChar ch = cur.currentChar(); + if (ch == opener) + count--; + else if (ch == close) + count++; + + if (count == 0) + return true; + } + } + + return false; +} + + +//END KateVarIndent + +//BEGIN KateScriptIndent +KateScriptIndent::KateScriptIndent( KateDocument *doc ) + : KateNormalIndent( doc ) +{ + m_script=KateFactory::self()->indentScript ("script-indent-c1-test"); +} + +KateScriptIndent::~KateScriptIndent() +{ +} + +void KateScriptIndent::processNewline( KateDocCursor &begin, bool needContinue ) +{ + kdDebug(13030) << "processNewline" << endl; + KateView *view = doc->activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processChar"<activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processChar"<activeView(); + + if (view) + { + QString errorMsg; + + QTime t; + t.start(); + kdDebug(13030)<<"calling m_script.processLine"< +ScriptIndentConfigPage::ScriptIndentConfigPage ( QWidget *parent, const char *name ) + : IndenterConfigPage(parent, name) +{ + QLabel* hello = new QLabel("Hello world! Dummy for testing purpose.", this); + hello->show(); +} + +ScriptIndentConfigPage::~ScriptIndentConfigPage () +{ +} + +void ScriptIndentConfigPage::apply () +{ + kdDebug(13030) << "ScriptIndentConfigPagE::apply() was called, save config options now!" << endl; +} +//END ScriptIndentConfigPage + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/kateautoindent.h b/kate/part/kateautoindent.h new file mode 100644 index 000000000..76ba14ee6 --- /dev/null +++ b/kate/part/kateautoindent.h @@ -0,0 +1,581 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Jesse Yurkovich + Copyright (C) 2004 >Anders Lund (KateVarIndent class) + Copyright (C) 2005 Dominik Haumann (basic support for config page) + + 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 02110-1301, USA. +*/ + +#ifndef __KATE_AUTO_INDENT_H__ +#define __KATE_AUTO_INDENT_H__ + +#include + +#include "katecursor.h" +#include "kateconfig.h" +#include "katejscript.h" +class KateDocument; + +/** + * This widget will be embedded into a modal dialog when clicking + * the "Configure..." button in the indentation config page. + * To add a config page for an indenter there are several todos: + * - Derive a class from this class. This widget will be embedded into + * the config dialog. + * - Override the slot @p apply(), which is called when the configuration + * needs to be saved. + * - Override @p KateAutoIndent::configPage() to return an instance of + * this dialog. + * - Return @p true in @p KateAutoIndent::hasConfigPage() for the + * corresponding indenter id. + */ +class IndenterConfigPage : public QWidget +{ + Q_OBJECT + + public: + /** + * Standard constructor + * @param parent parent widget + * @param name name + */ + IndenterConfigPage ( QWidget *parent=0, const char *name=0 ) : QWidget(parent, name) {} + virtual ~IndenterConfigPage () {} + + public slots: + /** + * Apply the changes. Save options here, use @p kapp->config() and + * group [Kate Indenter MyIndenter]. + */ + virtual void apply () = 0; +}; + +/** + * Provides Auto-Indent functionality for katepart. + * This baseclass is a real dummy, does nothing beside remembering the document it belongs too, + * only to have the object around + */ +class KateAutoIndent : public QObject +{ + Q_OBJECT + + /** + * Static methods to create and list indention modes + */ + public: + /** + * Create an indenter + * @param doc document for the indenter + * @param mode indention mode wanted + * @return created autoindention object + */ + static KateAutoIndent *createIndenter (KateDocument *doc, uint mode); + + /** + * List all possible modes by name + * @return list of modes + */ + static QStringList listModes (); + + /** + * Return the mode name given the mode + * @param mode mode index + * @return name for this mode index + */ + static QString modeName (uint mode); + + /** + * Return the mode description + * @param mode mode index + * @return mode index + */ + static QString modeDescription (uint mode); + + /** + * Maps name -> index + * @param name mode name + * @return mode index + */ + static uint modeNumber (const QString &name); + + /** + * Config page support + * @param mode mode index + * @return true, if the indenter @p mode has a configuration page + */ + static bool hasConfigPage (uint mode); + + /** + * Support for a config page. + * @return config page or 0 if not available. + */ + static IndenterConfigPage* configPage(QWidget *parent, uint mode); + + public: + /** + * Constructor + * @param doc parent document + */ + KateAutoIndent (KateDocument *doc); + + /** + * Virtual Destructor for the baseclass + */ + virtual ~KateAutoIndent (); + + public slots: + /** + * Update indenter's configuration (indention width, attributes etc.) + */ + virtual void updateConfig () {}; + + public: + /** + * does this indenter support processNewLine + * @return can you do it? + */ + virtual bool canProcessNewLine () const { return false; } + + /** + * Called every time a newline character is inserted in the document. + * + * @param cur The position to start processing. Contains the new cursor position after the indention. + * @param needContinue Used to determine whether to calculate a continue indent or not. + */ + virtual void processNewline (KateDocCursor &cur, bool needContinue) { Q_UNUSED(cur); Q_UNUSED(needContinue); } + + /** + * Called every time a character is inserted into the document. + * @param c character inserted + */ + virtual void processChar (QChar c) { Q_UNUSED(c); } + + /** + * Aligns/indents the given line to the proper indent position. + */ + virtual void processLine (KateDocCursor &/*line*/) { } + + /** + * Processes a section of text, indenting each line in between. + */ + virtual void processSection (const KateDocCursor &/*begin*/, const KateDocCursor &/*end*/) { } + + /** + * Set to true if an actual implementation of 'processLine' is present. + * This is used to prevent a needless Undo action from being created. + */ + virtual bool canProcessLine() const { return false; } + + /** + * Mode index of this mode + * @return modeNumber + */ + virtual uint modeNumber () const { return KateDocumentConfig::imNone; }; + + protected: + KateDocument *doc; +}; + +/** + * This action provides a list of available indenters and gets plugged + * into the KateView's KActionCollection. + */ +class KateViewIndentationAction : public KActionMenu +{ + Q_OBJECT + + public: + KateViewIndentationAction(KateDocument *_doc, const QString& text, QObject* parent = 0, const char* name = 0); + + ~KateViewIndentationAction(){;}; + + private: + KateDocument* doc; + + public slots: + void slotAboutToShow(); + + private slots: + void setMode (int mode); +}; + +/** + * Provides Auto-Indent functionality for katepart. + */ +class KateNormalIndent : public KateAutoIndent +{ + Q_OBJECT + +public: + /** + * Constructor + * @param doc parent document + */ + KateNormalIndent (KateDocument *doc); + + /** + * Virtual Destructor for the baseclass + */ + virtual ~KateNormalIndent (); + +public slots: + /** + * Update indenter's configuration (indention width, attributes etc.) + */ + virtual void updateConfig (); + +public: + /** + * does this indenter support processNewLine + * @return can you do it? + */ + virtual bool canProcessNewLine () const { return true; } + + /** + * Called every time a newline character is inserted in the document. + * + * @param cur The position to start processing. Contains the new cursor position after the indention. + * @param needContinue Used to determine whether to calculate a continue indent or not. + */ + virtual void processNewline (KateDocCursor &cur, bool needContinue); + + /** + * Called every time a character is inserted into the document. + * @param c character inserted + */ + virtual void processChar (QChar c) { Q_UNUSED(c); } + + /** + * Aligns/indents the given line to the proper indent position. + */ + virtual void processLine (KateDocCursor &/*line*/) { } + + /** + * Processes a section of text, indenting each line in between. + */ + virtual void processSection (const KateDocCursor &/*begin*/, const KateDocCursor &/*end*/) { } + + /** + * Set to true if an actual implementation of 'processLine' is present. + * This is used to prevent a needless Undo action from being created. + */ + virtual bool canProcessLine() const { return false; } + + /** + * Mode index of this mode + * @return modeNumber + */ + virtual uint modeNumber () const { return KateDocumentConfig::imNormal; }; + +protected: + + /** + * Determines if the characters open and close are balanced between @p begin and @p end + * Fills in @p pos with the column position of first opened character if found. + * + * @param begin Beginning cursor position. + * @param end Ending cursor position where the processing will stop. + * @param open The open character. + * @param close The closing character which should be matched against @p open. + * @param pos Contains the position of the first @p open character in the line. + * @return True if @p open and @p close have an equal number of occurances between @p begin and @p end. False otherwise. + */ + bool isBalanced (KateDocCursor &begin, const KateDocCursor &end, QChar open, QChar close, uint &pos) const; + + /** + * Skip all whitespace starting at @p cur and ending at @p max. Spans lines if @p newline is set. + * @p cur is set to the current position afterwards. + * + * @param cur The current cursor position to start from. + * @param max The furthest cursor position that will be used for processing + * @param newline Whether we are allowed to span multiple lines when skipping blanks + * @return True if @p cur < @p max after processing. False otherwise. + */ + bool skipBlanks (KateDocCursor &cur, KateDocCursor &max, bool newline) const; + + /** + * Measures the indention of the current textline marked by cur + * @param cur The cursor position to measure the indent to. + * @return The length of the indention in characters. + */ + uint measureIndent (KateDocCursor &cur) const; + + /** + * Produces a string with the proper indentation characters for its length. + * + * @param length The length of the indention in characters. + * @return A QString representing @p length characters (factoring in tabs and spaces) + */ + QString tabString(uint length) const; + + uint tabWidth; //!< The number of characters simulated for a tab + uint indentWidth; //!< The number of characters used when tabs are replaced by spaces + +public: + // Attributes that we should skip over or otherwise know about + uchar commentAttrib; + uchar doxyCommentAttrib; + uchar regionAttrib; + uchar symbolAttrib; + uchar alertAttrib; + uchar tagAttrib; + uchar wordAttrib; + uchar keywordAttrib; + uchar normalAttrib; + uchar extensionAttrib; + uchar preprocessorAttrib; + uchar stringAttrib; + uchar charAttrib; + +protected: + bool useSpaces; //!< Should we use spaces or tabs to indent + bool mixedIndent; //!< Optimize indent by mixing spaces and tabs, ala emacs + bool keepProfile; //!< Always try to honor the leading whitespace of lines already in the file +}; + +class KateCSmartIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateCSmartIndent (KateDocument *doc); + ~KateCSmartIndent (); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imCStyle; }; + + private: + uint calcIndent (KateDocCursor &begin, bool needContinue); + uint calcContinue (KateDocCursor &begin, KateDocCursor &end); + uint findOpeningBrace (KateDocCursor &start); + uint findOpeningParen (KateDocCursor &start); + uint findOpeningComment (KateDocCursor &start); + bool firstOpeningBrace (KateDocCursor &start); + bool handleDoxygen (KateDocCursor &begin); + + bool allowSemi; + bool processingBlock; +}; + +class KatePythonIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KatePythonIndent (KateDocument *doc); + ~KatePythonIndent (); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + + virtual uint modeNumber () const { return KateDocumentConfig::imPythonStyle; }; + + private: + int calcExtra (int &prevBlock, int &pos, KateDocCursor &end); + void traverseString( const QChar &stringChar, KateDocCursor &cur, KateDocCursor &end ); + + static QRegExp endWithColon; + static QRegExp stopStmt; + static QRegExp blockBegin; +}; + +class KateXmlIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateXmlIndent (KateDocument *doc); + ~KateXmlIndent (); + + virtual uint modeNumber () const { return KateDocumentConfig::imXmlStyle; } + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + virtual void processLine (KateDocCursor &line); + virtual bool canProcessLine() const { return true; } + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + private: + // sets the indentation of a single line based on previous line + // (returns indentation width) + uint processLine (uint line); + + // gets information about a line + void getLineInfo (uint line, uint &prevIndent, int &numTags, + uint &attrCol, bool &unclosedTag); + + // useful regular expressions + static const QRegExp startsWithCloseTag; + static const QRegExp unclosedDoctype; +}; + +class KateCSAndSIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateCSAndSIndent (KateDocument *doc); + ~KateCSAndSIndent (); + + virtual void processNewline (KateDocCursor &begin, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imCSAndS; }; + + private: + void updateIndentString(); + + bool inForStatement( int line ); + int lastNonCommentChar( const KateDocCursor &line ); + bool startsWithLabel( int line ); + bool inStatement( const KateDocCursor &begin ); + QString continuationIndent( const KateDocCursor &begin ); + + QString calcIndent (const KateDocCursor &begin); + QString calcIndentAfterKeyword(const KateDocCursor &indentCursor, const KateDocCursor &keywordCursor, int keywordPos, bool blockKeyword); + QString calcIndentInBracket(const KateDocCursor &indentCursor, const KateDocCursor &bracketCursor, int bracketPos); + QString calcIndentInBrace(const KateDocCursor &indentCursor, const KateDocCursor &braceCursor, int bracePos); + + bool handleDoxygen (KateDocCursor &begin); + QString findOpeningCommentIndentation (const KateDocCursor &start); + + QString indentString; +}; + +/** + * This indenter uses document variables to determine when to add/remove indents. + * + * It attempts to get the following variables from the document: + * - var-indent-indent-after: A rerular expression which will cause a line to + * be indented by one unit, if the first non-whitespace-only line above matches. + * - var-indent-indent: A regular expression, which will cause a matching line + * to be indented by one unit. + * - var-indent-unindent: A regular expression which will cause the line to be + * unindented by one unit if matching. + * - var-indent-triggerchars: a list of characters that should cause the + * indentiou to be recalculated immediately when typed. + * - var-indent-handle-couples: a list of paren sets to handle. Any combination + * of 'parens' 'braces' and 'brackets'. Each set type is handled + * the following way: If there are unmatched opening instances on the above line, + * one indent unit is added, if there are unmatched closing instances on the + * current line, one indent unit is removed. + * - var-indent-couple-attribute: When looking for unmatched couple openings/closings, + * only characters with this attribute is considered. The value must be the + * attribute name from the syntax xml file, for example "Symbol". If it's not + * specified, attribute 0 is used (usually 'Normal Text'). + * + * The idea is to provide a somewhat intelligent indentation for perl, php, + * bash, scheme and in general formats with humble indentation needs. + */ +class KateVarIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + /** + * Purely for readability, couples we know and love + */ + enum pairs { + Parens=1, + Braces=2, + Brackets=4, + AngleBrackets=8 + }; + + KateVarIndent( KateDocument *doc ); + virtual ~KateVarIndent(); + + virtual void processNewline (KateDocCursor &cur, bool needContinue); + virtual void processChar (QChar c); + + virtual void processLine (KateDocCursor &line); + virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imVarIndent; }; + + private slots: + void slotVariableChanged(const QString&, const QString&); + + private: + /** + * Check if coupled characters are in balance within one line. + * @param line the line to check + * @param open the opening character + * @param close the closing character + * @param attrib the attribute the characters must have, defaults to + * KateAutoIndent::symbolAttrib + */ + int coupleBalance( int line, const QChar &open, const QChar &close ) const; + + /** + * @return true if there is a matching opening with the correct attribute + * @param end a cursor pointing to the closing character + */ + bool hasRelevantOpening( const KateDocCursor &end ) const; + + class KateVarIndentPrivate *d; +}; + +class KateScriptIndent : public KateNormalIndent +{ + Q_OBJECT + + public: + KateScriptIndent( KateDocument *doc ); + ~KateScriptIndent(); + + virtual void processNewline( KateDocCursor &cur, bool needContinue ); + virtual void processChar( QChar c ); + + virtual void processLine (KateDocCursor &line); +// virtual void processSection (const KateDocCursor &begin, const KateDocCursor &end); + + virtual bool canProcessLine() const { return true; } + + virtual uint modeNumber () const { return KateDocumentConfig::imScriptIndent; }; + private: + KateIndentScript m_script; +}; + +class ScriptIndentConfigPage : public IndenterConfigPage +{ + Q_OBJECT + + public: + ScriptIndentConfigPage ( QWidget *parent=0, const char *name=0 ); + virtual ~ScriptIndentConfigPage (); + + public slots: + /** + * Apply changes. + */ + virtual void apply (); +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebookmarks.cpp b/kate/part/katebookmarks.cpp new file mode 100644 index 000000000..f92fb4282 --- /dev/null +++ b/kate/part/katebookmarks.cpp @@ -0,0 +1,287 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003, 2004 Anders Lund + Copyright (C) 2002 John Firebaugh + + 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 02110-1301, USA. +*/ + +#include "katebookmarks.h" +#include "katebookmarks.moc" + +#include "katedocument.h" +#include "kateview.h" + +#include +#include +#include +#include +#include +#include + +#include +#include +#include + +/** + Utility: selection sort + sort a QMemArray in ascending order. + max it the largest (zerobased) index to sort. + To sort the entire array: ssort( *array, array.size() -1 ); + This is only efficient if ran only once. +*/ +static void ssort( QMemArray &a, int max ) +{ + uint tmp, j, maxpos; + for ( uint h = max; h >= 1; h-- ) + { + maxpos = 0; + for ( j = 0; j <= h; j++ ) + maxpos = a[j] > a[maxpos] ? j : maxpos; + tmp = a[maxpos]; + a[maxpos] = a[h]; + a[h] = tmp; + } +} + +// TODO add a insort() or bubble_sort - more efficient for aboutToShow() ? + +KateBookmarks::KateBookmarks( KateView* view, Sorting sort ) + : QObject( view, "kate bookmarks" ) + , m_view( view ) + , m_sorting( sort ) +{ + connect (view->getDoc(), SIGNAL(marksChanged()), this, SLOT(marksChanged())); + _tries=0; + m_bookmarksMenu = 0L; +} + +KateBookmarks::~KateBookmarks() +{ +} + +void KateBookmarks::createActions( KActionCollection* ac ) +{ + m_bookmarkToggle = new KToggleAction( + i18n("Set &Bookmark"), "bookmark", CTRL+Key_B, + this, SLOT(toggleBookmark()), + ac, "bookmarks_toggle" ); + m_bookmarkToggle->setWhatsThis(i18n("If a line has no bookmark then add one, otherwise remove it.")); + m_bookmarkToggle->setCheckedState( i18n("Clear &Bookmark") ); + + m_bookmarkClear = new KAction( + i18n("Clear &All Bookmarks"), 0, + this, SLOT(clearBookmarks()), + ac, "bookmarks_clear"); + m_bookmarkClear->setWhatsThis(i18n("Remove all bookmarks of the current document.")); + + m_goNext = new KAction( + i18n("Next Bookmark"), "next", ALT + Key_PageDown, + this, SLOT(goNext()), + ac, "bookmarks_next"); + m_goNext->setWhatsThis(i18n("Go to the next bookmark.")); + + m_goPrevious = new KAction( + i18n("Previous Bookmark"), "previous", ALT + Key_PageUp, + this, SLOT(goPrevious()), + ac, "bookmarks_previous"); + m_goPrevious->setWhatsThis(i18n("Go to the previous bookmark.")); + + m_bookmarksMenu = (new KActionMenu(i18n("&Bookmarks"), ac, "bookmarks"))->popupMenu(); + + //connect the aboutToShow() and aboutToHide() signals with + //the bookmarkMenuAboutToShow() and bookmarkMenuAboutToHide() slots + connect( m_bookmarksMenu, SIGNAL(aboutToShow()), this, SLOT(bookmarkMenuAboutToShow())); + connect( m_bookmarksMenu, SIGNAL(aboutToHide()), this, SLOT(bookmarkMenuAboutToHide()) ); + + marksChanged (); + bookmarkMenuAboutToHide(); + + connect( m_view, SIGNAL( gotFocus( Kate::View * ) ), this, SLOT( slotViewGotFocus( Kate::View * ) ) ); + connect( m_view, SIGNAL( lostFocus( Kate::View * ) ), this, SLOT( slotViewLostFocus( Kate::View * ) ) ); +} + +void KateBookmarks::toggleBookmark () +{ + uint mark = m_view->getDoc()->mark( m_view->cursorLine() ); + if( mark & KTextEditor::MarkInterface::markType01 ) + m_view->getDoc()->removeMark( m_view->cursorLine(), + KTextEditor::MarkInterface::markType01 ); + else + m_view->getDoc()->addMark( m_view->cursorLine(), + KTextEditor::MarkInterface::markType01 ); +} + +void KateBookmarks::clearBookmarks () +{ + + QPtrList m = m_view->getDoc()->marks(); + for (uint i=0; i < m.count(); i++) + m_view->getDoc()->removeMark( m.at(i)->line, KTextEditor::MarkInterface::markType01 ); + + // just to be sure ;) + marksChanged (); +} + +void KateBookmarks::slotViewGotFocus( Kate::View *v ) +{ + if ( v == (Kate::View*)m_view ) + bookmarkMenuAboutToHide(); +} + +void KateBookmarks::slotViewLostFocus( Kate::View *v ) +{ + if ( v == (Kate::View*)m_view ) + m_bookmarksMenu->clear(); +} + +void KateBookmarks::insertBookmarks( QPopupMenu& menu ) +{ + uint line = m_view->cursorLine(); + const QRegExp re("&(?!&)"); + int idx( -1 ); + int old_menu_count = menu.count(); + KTextEditor::Mark *next = 0; + KTextEditor::Mark *prev = 0; + + QPtrList m = m_view->getDoc()->marks(); + QMemArray sortArray( m.count() ); + QPtrListIterator it( m ); + + if ( it.count() > 0 ) + menu.insertSeparator(); + + for( int i = 0; *it; ++it, ++i ) + { + if( (*it)->type & KTextEditor::MarkInterface::markType01 ) + { + QString bText = KStringHandler::rEmSqueeze + ( m_view->getDoc()->textLine( (*it)->line ), + menu.fontMetrics(), 32 ); + bText.replace(re, "&&"); // kill undesired accellerators! + bText.replace('\t', ' '); // kill tabs, as they are interpreted as shortcuts + + if ( m_sorting == Position ) + { + sortArray[i] = (*it)->line; + ssort( sortArray, i ); + idx = sortArray.find( (*it)->line ) + 3; + } + + menu.insertItem( + QString("%1 - \"%2\"").arg( (*it)->line+1 ).arg( bText ), + m_view, SLOT(gotoLineNumber(int)), 0, (*it)->line, idx ); + + if ( (*it)->line < line ) + { + if ( ! prev || prev->line < (*it)->line ) + prev = (*it); + } + + else if ( (*it)->line > line ) + { + if ( ! next || next->line > (*it)->line ) + next = (*it); + } + } + } + + idx = ++old_menu_count; + if ( next ) + { + m_goNext->setText( i18n("&Next: %1 - \"%2\"").arg( next->line + 1 ) + .arg( KStringHandler::rsqueeze( m_view->getDoc()->textLine( next->line ), 24 ) ) ); + m_goNext->plug( &menu, idx ); + idx++; + } + if ( prev ) + { + m_goPrevious->setText( i18n("&Previous: %1 - \"%2\"").arg(prev->line + 1 ) + .arg( KStringHandler::rsqueeze( m_view->getDoc()->textLine( prev->line ), 24 ) ) ); + m_goPrevious->plug( &menu, idx ); + idx++; + } + if ( next || prev ) + menu.insertSeparator( idx ); + +} + +void KateBookmarks::bookmarkMenuAboutToShow() +{ + + QPtrList m = m_view->getDoc()->marks(); + + m_bookmarksMenu->clear(); + m_bookmarkToggle->setChecked( m_view->getDoc()->mark( m_view->cursorLine() ) + & KTextEditor::MarkInterface::markType01 ); + m_bookmarkToggle->plug( m_bookmarksMenu ); + m_bookmarkClear->plug( m_bookmarksMenu ); + + + insertBookmarks(*m_bookmarksMenu); +} + +/* + Make sure next/prev actions are plugged, and have a clean text +*/ +void KateBookmarks::bookmarkMenuAboutToHide() +{ + m_bookmarkToggle->plug( m_bookmarksMenu ); + m_bookmarkClear->plug( m_bookmarksMenu ); + m_goNext->setText( i18n("Next Bookmark") ); + m_goNext->plug( m_bookmarksMenu ); + m_goPrevious->setText( i18n("Previous Bookmark") ); + m_goPrevious->plug( m_bookmarksMenu ); +} + +void KateBookmarks::goNext() +{ + QPtrList m = m_view->getDoc()->marks(); + if (m.isEmpty()) + return; + + uint line = m_view->cursorLine(); + int found = -1; + + for (uint z=0; z < m.count(); z++) + if ( (m.at(z)->line > line) && ((found == -1) || (uint(found) > m.at(z)->line)) ) + found = m.at(z)->line; + + if (found != -1) + m_view->gotoLineNumber ( found ); +} + +void KateBookmarks::goPrevious() +{ + QPtrList m = m_view->getDoc()->marks(); + if (m.isEmpty()) + return; + + uint line = m_view->cursorLine(); + int found = -1; + + for (uint z=0; z < m.count(); z++) + if ((m.at(z)->line < line) && ((found == -1) || (uint(found) < m.at(z)->line))) + found = m.at(z)->line; + + if (found != -1) + m_view->gotoLineNumber ( found ); +} + +void KateBookmarks::marksChanged () +{ + m_bookmarkClear->setEnabled( !m_view->getDoc()->marks().isEmpty() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebookmarks.h b/kate/part/katebookmarks.h new file mode 100644 index 000000000..0d72c0ccc --- /dev/null +++ b/kate/part/katebookmarks.h @@ -0,0 +1,86 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002, 2003 Anders Lund + Copyright (C) 2002 John Firebaugh + + 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 02110-1301, USA. +*/ + +#ifndef __KATE_BOOKMARKS_H__ +#define __KATE_BOOKMARKS_H__ + +#include +#include + +class KateView; + +namespace KTextEditor { class Mark; } + +namespace Kate { class View; } + +class KAction; +class KToggleAction; +class KActionCollection; +class QPopupMenu; +class QMenuData; + +class KateBookmarks : public QObject +{ + Q_OBJECT + + public: + enum Sorting { Position, Creation }; + KateBookmarks( KateView* parent, Sorting sort=Position ); + virtual ~KateBookmarks(); + + void createActions( KActionCollection* ); + + KateBookmarks::Sorting sorting() { return m_sorting; }; + void setSorting( Sorting s ) { m_sorting = s; }; + + protected: + void insertBookmarks( QPopupMenu& menu); + + private slots: + void toggleBookmark(); + void clearBookmarks(); + + void slotViewGotFocus( Kate::View * ); + void slotViewLostFocus( Kate::View * ); + + void bookmarkMenuAboutToShow(); + void bookmarkMenuAboutToHide(); + + void goNext(); + void goPrevious(); + + void marksChanged (); + + private: + KateView* m_view; + KToggleAction* m_bookmarkToggle; + KAction* m_bookmarkClear; + KAction* m_goNext; + KAction* m_goPrevious; + + Sorting m_sorting; + QPopupMenu* m_bookmarksMenu; + + uint _tries; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; +// vim: noet ts=2 diff --git a/kate/part/katebuffer.cpp b/kate/part/katebuffer.cpp new file mode 100644 index 000000000..06c919f96 --- /dev/null +++ b/kate/part/katebuffer.cpp @@ -0,0 +1,1660 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Waldo Bastian + Copyright (C) 2002-2004 Christoph Cullmann + + 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 02110-1301, USA. +*/ + +#include +#include +#include + +#include "katebuffer.h" +#include "katebuffer.moc" + +#include "katedocument.h" +#include "katehighlight.h" +#include "kateconfig.h" +#include "katefactory.h" +#include "kateautoindent.h" + +#include +#include +#include + +#include +#include +#include +#include +#include +#include +#include + +/** + * loader block size, load 256 kb at once per default + * if file size is smaller, fall back to file size + */ +static const Q_ULONG KATE_FILE_LOADER_BS = 256 * 1024; + +/** + * KATE_AVG_BLOCK_SIZE is in characters ! + * (internaly we calc with approx 80 chars per line !) + * block will max contain around BLOCK_SIZE chars or + * BLOCK_LINES lines (after load, later that won't be tracked) + */ +static const Q_ULONG KATE_AVG_BLOCK_SIZE = 2048 * 80; +static const Q_ULONG KATE_MAX_BLOCK_LINES = 2048; + +/** + * hl will look at the next KATE_HL_LOOKAHEAD lines + * or until the current block ends if a line is requested + * will avoid to run doHighlight too often + */ +static const uint KATE_HL_LOOKAHEAD = 64; + +/** + * KATE_MAX_BLOCKS_LOADED should be at least 4, as some + * methodes will cause heavy trashing, if not at least the + * latest 2-3 used blocks are alive + */ +uint KateBuffer::m_maxLoadedBlocks = 16; + +/** + * Initial value for m_maxDynamicContexts + */ +static const uint KATE_MAX_DYNAMIC_CONTEXTS = 512; + +void KateBuffer::setMaxLoadedBlocks (uint count) +{ + m_maxLoadedBlocks = kMax (4U, count); +} + +class KateFileLoader +{ + public: + KateFileLoader (const QString &filename, QTextCodec *codec, bool removeTrailingSpaces) + : m_file (filename) + , m_buffer (kMin (m_file.size(), KATE_FILE_LOADER_BS)) + , m_codec (codec) + , m_decoder (m_codec->makeDecoder()) + , m_position (0) + , m_lastLineStart (0) + , m_eof (false) // default to not eof + , lastWasEndOfLine (true) // at start of file, we had a virtual newline + , lastWasR (false) // we have not found a \r as last char + , m_eol (-1) // no eol type detected atm + , m_twoByteEncoding (QString(codec->name()) == "ISO-10646-UCS-2") + , m_binary (false) + , m_removeTrailingSpaces (removeTrailingSpaces) + { + kdDebug (13020) << "OPEN USES ENCODING: " << m_codec->name() << endl; + } + + ~KateFileLoader () + { + delete m_decoder; + } + + /** + * open file, read first chunk of data, detect eol + */ + bool open () + { + if (m_file.open (IO_ReadOnly)) + { + int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); + + if (c > 0) + { + // fix utf16 LE, stolen from khtml ;) + if ((c >= 2) && (m_codec->mibEnum() == 1000) && (m_buffer[1] == 0x00)) + { + // utf16LE, we need to put the decoder in LE mode + char reverseUtf16[3] = {0xFF, 0xFE, 0x00}; + m_decoder->toUnicode(reverseUtf16, 2); + } + + processNull (c); + m_text = m_decoder->toUnicode (m_buffer, c); + } + + m_eof = (c == -1) || (c == 0) || (m_text.length() == 0) || m_file.atEnd(); + + for (uint i=0; i < m_text.length(); i++) + { + if (m_text[i] == '\n') + { + m_eol = KateDocumentConfig::eolUnix; + break; + } + else if ((m_text[i] == '\r')) + { + if (((i+1) < m_text.length()) && (m_text[i+1] == '\n')) + { + m_eol = KateDocumentConfig::eolDos; + break; + } + else + { + m_eol = KateDocumentConfig::eolMac; + break; + } + } + } + + return true; + } + + return false; + } + + // no new lines around ? + inline bool eof () const { return m_eof && !lastWasEndOfLine && (m_lastLineStart == m_text.length()); } + + // eol mode ? autodetected on open(), -1 for no eol found in the first block! + inline int eol () const { return m_eol; } + + // binary ? + inline bool binary () const { return m_binary; } + + // should spaces be ignored at end of line? + inline bool removeTrailingSpaces () const { return m_removeTrailingSpaces; } + + // internal unicode data array + inline const QChar *unicode () const { return m_text.unicode(); } + + // read a line, return length + offset in unicode data + void readLine (uint &offset, uint &length) + { + length = 0; + offset = 0; + + while (m_position <= m_text.length()) + { + if (m_position == m_text.length()) + { + // try to load more text if something is around + if (!m_eof) + { + int c = m_file.readBlock (m_buffer.data(), m_buffer.size()); + + uint readString = 0; + if (c > 0) + { + processNull (c); + + QString str (m_decoder->toUnicode (m_buffer, c)); + readString = str.length(); + + m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart) + + str; + } + else + m_text = m_text.mid (m_lastLineStart, m_position-m_lastLineStart); + + // is file completly read ? + m_eof = (c == -1) || (c == 0) || (readString == 0) || m_file.atEnd(); + + // recalc current pos and last pos + m_position -= m_lastLineStart; + m_lastLineStart = 0; + } + + // oh oh, end of file, escape ! + if (m_eof && (m_position == m_text.length())) + { + lastWasEndOfLine = false; + + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position; + + return; + } + } + + if (m_text[m_position] == '\n') + { + lastWasEndOfLine = true; + + if (lastWasR) + { + m_lastLineStart++; + lastWasR = false; + } + else + { + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position+1; + m_position++; + + return; + } + } + else if (m_text[m_position] == '\r') + { + lastWasEndOfLine = true; + lastWasR = true; + + // line data + offset = m_lastLineStart; + length = m_position-m_lastLineStart; + + m_lastLineStart = m_position+1; + m_position++; + + return; + } + else + { + lastWasEndOfLine = false; + lastWasR = false; + } + + m_position++; + } + } + + // this nice methode will kill all 0 bytes (or double bytes) + // and remember if this was a binary or not ;) + void processNull (uint length) + { + if (m_twoByteEncoding) + { + for (uint i=1; i < length; i+=2) + { + if ((m_buffer[i] == 0) && (m_buffer[i-1] == 0)) + { + m_binary = true; + m_buffer[i] = ' '; + } + } + } + else + { + for (uint i=0; i < length; i++) + { + if (m_buffer[i] == 0) + { + m_binary = true; + m_buffer[i] = ' '; + } + } + } + } + + private: + QFile m_file; + QByteArray m_buffer; + QTextCodec *m_codec; + QTextDecoder *m_decoder; + QString m_text; + uint m_position; + uint m_lastLineStart; + bool m_eof; + bool lastWasEndOfLine; + bool lastWasR; + int m_eol; + bool m_twoByteEncoding; + bool m_binary; + bool m_removeTrailingSpaces; +}; + +/** + * Create an empty buffer. (with one block with one empty line) + */ +KateBuffer::KateBuffer(KateDocument *doc) + : QObject (doc), + editSessionNumber (0), + editIsRunning (false), + editTagLineStart (0xffffffff), + editTagLineEnd (0), + editTagLineFrom (false), + editChangesDone (false), + m_doc (doc), + m_lines (0), + m_lastInSyncBlock (0), + m_lastFoundBlock (0), + m_cacheReadError(false), + m_cacheWriteError(false), + m_loadingBorked (false), + m_binary (false), + m_highlight (0), + m_regionTree (this), + m_tabWidth (8), + m_lineHighlightedMax (0), + m_lineHighlighted (0), + m_maxDynamicContexts (KATE_MAX_DYNAMIC_CONTEXTS) +{ + clear(); +} + +/** + * Cleanup on destruction + */ +KateBuffer::~KateBuffer() +{ + // DELETE ALL BLOCKS, will free mem + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + // release HL + if (m_highlight) + m_highlight->release(); +} + +void KateBuffer::editStart () +{ + editSessionNumber++; + + if (editSessionNumber > 1) + return; + + editIsRunning = true; + + editTagLineStart = 0xffffffff; + editTagLineEnd = 0; + editTagLineFrom = false; + + editChangesDone = false; +} + +void KateBuffer::editEnd () +{ + if (editSessionNumber == 0) + return; + + editSessionNumber--; + + if (editSessionNumber > 0) + return; + + if (editChangesDone) + { + // hl update !!! + if ( m_highlight && !m_highlight->noHighlighting() + && (editTagLineStart <= editTagLineEnd) + && (editTagLineEnd <= m_lineHighlighted)) + { + // look one line too far, needed for linecontinue stuff + editTagLineEnd++; + + // look one line before, needed nearly 100% only for indentation based folding ! + if (editTagLineStart > 0) + editTagLineStart--; + + KateBufBlock *buf2 = 0; + bool needContinue = false; + while ((buf2 = findBlock(editTagLineStart))) + { + needContinue = doHighlight (buf2, + (editTagLineStart > buf2->startLine()) ? editTagLineStart : buf2->startLine(), + (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd, + true); + + editTagLineStart = (editTagLineEnd > buf2->endLine()) ? buf2->endLine() : editTagLineEnd; + + if ((editTagLineStart >= m_lines) || (editTagLineStart >= editTagLineEnd)) + break; + } + + if (needContinue) + m_lineHighlighted = editTagLineStart; + + if (editTagLineStart > m_lineHighlightedMax) + m_lineHighlightedMax = editTagLineStart; + } + else if (editTagLineStart < m_lineHighlightedMax) + m_lineHighlightedMax = editTagLineStart; + } + + editIsRunning = false; +} + +void KateBuffer::clear() +{ + m_regionTree.clear(); + + // cleanup the blocks + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + m_blocks.clear (); + + // create a bufblock with one line, we need that, only in openFile we won't have that + KateBufBlock *block = new KateBufBlock(this, 0, 0); + m_blocks.append (block); + + // reset the state + m_lines = block->lines(); + m_lastInSyncBlock = 0; + m_lastFoundBlock = 0; + m_cacheWriteError = false; + m_cacheReadError = false; + m_loadingBorked = false; + m_binary = false; + + m_lineHighlightedMax = 0; + m_lineHighlighted = 0; +} + +bool KateBuffer::openFile (const QString &m_file) +{ + KateFileLoader file (m_file, m_doc->config()->codec(), m_doc->configFlags() & KateDocument::cfRemoveSpaces); + + bool ok = false; + struct stat sbuf; + if (stat(QFile::encodeName(m_file), &sbuf) == 0) + { + if (S_ISREG(sbuf.st_mode) && file.open()) + ok = true; + } + + if (!ok) + { + clear(); + return false; // Error + } + + // set eol mode, if a eol char was found in the first 256kb block and we allow this at all! + if (m_doc->config()->allowEolDetection() && (file.eol() != -1)) + m_doc->config()->setEol (file.eol()); + + // flush current content + clear (); + + // cleanup the blocks + for (uint i=0; i < m_blocks.size(); i++) + delete m_blocks[i]; + + m_blocks.clear (); + + // do the real work + KateBufBlock *block = 0; + m_lines = 0; + while (!file.eof() && !m_cacheWriteError) + { + block = new KateBufBlock (this, block, 0, &file); + + m_lines = block->endLine (); + + if (m_cacheWriteError || (block->lines() == 0)) + { + delete block; + break; + } + else + m_blocks.append (block); + } + + // we had a cache write error, this load is really borked ! + if (m_cacheWriteError) + m_loadingBorked = true; + + if (m_blocks.isEmpty() || (m_lines == 0)) + { + // file was really empty, clean the buffers + emit the line changed + // loadingBorked will be false for such files, not matter what happened + // before + clear (); + } + else + { + // fix region tree + m_regionTree.fixRoot (m_lines); + } + + // if we have no hl or the "None" hl activated, whole file is correct highlighted + // after loading, which wonder ;) + if (!m_highlight || m_highlight->noHighlighting()) + { + m_lineHighlighted = m_lines; + m_lineHighlightedMax = m_lines; + } + + // binary? + m_binary = file.binary (); + + kdDebug (13020) << "LOADING DONE" << endl; + + return !m_loadingBorked; +} + +bool KateBuffer::canEncode () +{ + QTextCodec *codec = m_doc->config()->codec(); + + kdDebug(13020) << "ENC NAME: " << codec->name() << endl; + + // hardcode some unicode encodings which can encode all chars + if ((QString(codec->name()) == "UTF-8") || (QString(codec->name()) == "ISO-10646-UCS-2")) + return true; + + for (uint i=0; i < m_lines; i++) + { + if (!codec->canEncode (plainLine(i)->string())) + { + kdDebug(13020) << "STRING LINE: " << plainLine(i)->string() << endl; + kdDebug(13020) << "ENC WORKING: FALSE" << endl; + + return false; + } + } + + return true; +} + +bool KateBuffer::saveFile (const QString &m_file) +{ + QFile file (m_file); + QTextStream stream (&file); + + if ( !file.open( IO_WriteOnly ) ) + { + return false; // Error + } + + QTextCodec *codec = m_doc->config()->codec(); + + // disable Unicode headers + stream.setEncoding(QTextStream::RawUnicode); + + // this line sets the mapper to the correct codec + stream.setCodec(codec); + + // our loved eol string ;) + QString eol = m_doc->config()->eolString (); + + // should we strip spaces? + bool removeTrailingSpaces = m_doc->configFlags() & KateDocument::cfRemoveSpaces; + + // just dump the lines out ;) + for (uint i=0; i < m_lines; i++) + { + KateTextLine::Ptr textline = plainLine(i); + + // strip spaces + if (removeTrailingSpaces) + { + int lastChar = textline->lastChar(); + + if (lastChar > -1) + { + stream << QConstString (textline->text(), lastChar+1).string(); + } + } + else // simple, dump the line + stream << textline->string(); + + if ((i+1) < m_lines) + stream << eol; + } + + file.close (); + + m_loadingBorked = false; + + return (file.status() == IO_Ok); +} + +KateTextLine::Ptr KateBuffer::line_internal (KateBufBlock *buf, uint i) +{ + // update hl until this line + max KATE_HL_LOOKAHEAD + KateBufBlock *buf2 = 0; + while ((i >= m_lineHighlighted) && (buf2 = findBlock(m_lineHighlighted))) + { + uint end = kMin(i + KATE_HL_LOOKAHEAD, buf2->endLine()); + + doHighlight ( buf2, + kMax(m_lineHighlighted, buf2->startLine()), + end, + false ); + + m_lineHighlighted = end; + } + + // update hl max + if (m_lineHighlighted > m_lineHighlightedMax) + m_lineHighlightedMax = m_lineHighlighted; + + return buf->line (i - buf->startLine()); +} + +KateBufBlock *KateBuffer::findBlock_internal (uint i, uint *index) +{ + uint lastLine = m_blocks[m_lastInSyncBlock]->endLine (); + + if (lastLine > i) // we are in a allready known area ! + { + while (true) + { + KateBufBlock *buf = m_blocks[m_lastFoundBlock]; + + if ( (buf->startLine() <= i) + && (buf->endLine() > i) ) + { + if (index) + (*index) = m_lastFoundBlock; + + return m_blocks[m_lastFoundBlock]; + } + + if (i < buf->startLine()) + m_lastFoundBlock--; + else + m_lastFoundBlock++; + } + } + else // we need first to resync the startLines ! + { + if ((m_lastInSyncBlock+1) < m_blocks.size()) + m_lastInSyncBlock++; + else + return 0; + + for (; m_lastInSyncBlock < m_blocks.size(); m_lastInSyncBlock++) + { + // get next block + KateBufBlock *buf = m_blocks[m_lastInSyncBlock]; + + // sync startLine ! + buf->setStartLine (lastLine); + + // is it allready the searched block ? + if ((i >= lastLine) && (i < buf->endLine())) + { + // remember this block as last found ! + m_lastFoundBlock = m_lastInSyncBlock; + + if (index) + (*index) = m_lastFoundBlock; + + return buf; + } + + // increase lastLine with blocklinecount + lastLine += buf->lines (); + } + } + + // no block found ! + // index will not be set to any useful value in this case ! + return 0; +} + +void KateBuffer::changeLine(uint i) +{ + KateBufBlock *buf = findBlock(i); + + if (!buf) + return; + + // mark this block dirty + buf->markDirty (); + + // mark buffer changed + editChangesDone = true; + + // tag this line as changed + if (i < editTagLineStart) + editTagLineStart = i; + + if (i > editTagLineEnd) + editTagLineEnd = i; +} + +void KateBuffer::insertLine(uint i, KateTextLine::Ptr line) +{ + uint index = 0; + KateBufBlock *buf; + if (i == m_lines) + buf = findBlock(i-1, &index); + else + buf = findBlock(i, &index); + + if (!buf) + return; + + buf->insertLine(i - buf->startLine(), line); + + if (m_lineHighlightedMax > i) + m_lineHighlightedMax++; + + if (m_lineHighlighted > i) + m_lineHighlighted++; + + m_lines++; + + // last sync block adjust + if (m_lastInSyncBlock > index) + m_lastInSyncBlock = index; + + // last found + if (m_lastInSyncBlock < m_lastFoundBlock) + m_lastFoundBlock = m_lastInSyncBlock; + + // mark buffer changed + editChangesDone = true; + + // tag this line as inserted + if (i < editTagLineStart) + editTagLineStart = i; + + if (i <= editTagLineEnd) + editTagLineEnd++; + + if (i > editTagLineEnd) + editTagLineEnd = i; + + // line inserted + editTagLineFrom = true; + + m_regionTree.lineHasBeenInserted (i); +} + +void KateBuffer::removeLine(uint i) +{ + uint index = 0; + KateBufBlock *buf = findBlock(i, &index); + + if (!buf) + return; + + buf->removeLine(i - buf->startLine()); + + if (m_lineHighlightedMax > i) + m_lineHighlightedMax--; + + if (m_lineHighlighted > i) + m_lineHighlighted--; + + m_lines--; + + // trash away a empty block + if (buf->lines() == 0) + { + // we need to change which block is last in sync + if (m_lastInSyncBlock >= index) + { + m_lastInSyncBlock = index; + + if (buf->next()) + { + if (buf->prev()) + buf->next()->setStartLine (buf->prev()->endLine()); + else + buf->next()->setStartLine (0); + } + } + + // cu block ! + delete buf; + m_blocks.erase (m_blocks.begin()+index); + + // make sure we don't keep a pointer to the deleted block + if( m_lastInSyncBlock >= index ) + m_lastInSyncBlock = index - 1; + } + else + { + // last sync block adjust + if (m_lastInSyncBlock > index) + m_lastInSyncBlock = index; + } + + // last found + if (m_lastInSyncBlock < m_lastFoundBlock) + m_lastFoundBlock = m_lastInSyncBlock; + + // mark buffer changed + editChangesDone = true; + + // tag this line as removed + if (i < editTagLineStart) + editTagLineStart = i; + + if (i < editTagLineEnd) + editTagLineEnd--; + + if (i > editTagLineEnd) + editTagLineEnd = i; + + // line removed + editTagLineFrom = true; + + m_regionTree.lineHasBeenRemoved (i); +} + +void KateBuffer::setTabWidth (uint w) +{ + if ((m_tabWidth != w) && (m_tabWidth > 0)) + { + m_tabWidth = w; + + if (m_highlight && m_highlight->foldingIndentationSensitive()) + invalidateHighlighting(); + } +} + +void KateBuffer::setHighlight(uint hlMode) +{ + KateHighlighting *h = KateHlManager::self()->getHl(hlMode); + + // aha, hl will change + if (h != m_highlight) + { + bool invalidate = !h->noHighlighting(); + + if (m_highlight) + { + m_highlight->release(); + invalidate = true; + } + + h->use(); + + // Clear code folding tree (see bug #124102) + m_regionTree.clear(); + m_regionTree.fixRoot(m_lines); + + // try to set indentation + if (!h->indentation().isEmpty()) + m_doc->config()->setIndentationMode (KateAutoIndent::modeNumber(h->indentation())); + + m_highlight = h; + + if (invalidate) + invalidateHighlighting(); + + // inform the document that the hl was really changed + // needed to update attributes and more ;) + m_doc->bufferHlChanged (); + } +} + +void KateBuffer::invalidateHighlighting() +{ + m_lineHighlightedMax = 0; + m_lineHighlighted = 0; +} + + +void KateBuffer::updatePreviousNotEmptyLine(KateBufBlock *blk,uint current_line,bool addindent,uint deindent) +{ + KateTextLine::Ptr textLine; + do { + if (current_line>0) current_line--; + else + { + uint line=blk->startLine()+current_line; + if (line==0) return; + line--; + blk=findBlock(line); + if (!blk) { + kdDebug(13020)<<"updatePreviousNotEmptyLine: block not found, this must not happen"<startLine(); + } + textLine = blk->line(current_line); + } while (textLine->firstChar()==-1); + kdDebug(13020)<<"updatePreviousNotEmptyLine: updating line:"<<(blk->startLine()+current_line)< foldingList=textLine->foldingListArray(); + while ( (foldingList.size()>0) && ( abs(foldingList[foldingList.size()-2])==1)) { + foldingList.resize(foldingList.size()-2,QGArray::SpeedOptim); + } + addIndentBasedFoldingInformation(foldingList,addindent,deindent); + textLine->setFoldingList(foldingList); + bool retVal_folding = false; + m_regionTree.updateLine (current_line + blk->startLine(), &foldingList, &retVal_folding, true,false); + emit tagLines (blk->startLine()+current_line, blk->startLine()+current_line); +} + +void KateBuffer::addIndentBasedFoldingInformation(QMemArray &foldingList,bool addindent,uint deindent) +{ + if (addindent) { + //kdDebug(13020)<<"adding indent for line :"<startLine()<<" textLine->noIndentBasedFoldingAtStart"<noIndentBasedFoldingAtStart()< 0) + { + foldingList.resize (foldingList.size() + (deindent*2), QGArray::SpeedOptim); + + for (uint z= foldingList.size()-(deindent*2); z < foldingList.size(); z=z+2) + { + foldingList[z] = -1; + foldingList[z+1] = 0; + } + } +} + +bool KateBuffer::doHighlight (KateBufBlock *buf, uint startLine, uint endLine, bool invalidate) +{ + // no hl around, no stuff to do + if (!m_highlight) + return false; + + /*if (m_highlight->foldingIndentationSensitive()) + { + startLine=0; + endLine=50; + }*/ + + // we tried to start in a line behind this buf block ! + if (startLine >= (buf->startLine()+buf->lines())) + return false; + + //QTime t; + //t.start(); + //kdDebug (13020) << "HIGHLIGHTED START --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; + //kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; + //kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; + + // see if there are too many dynamic contexts; if yes, invalidate HL of all documents + if (KateHlManager::self()->countDynamicCtxs() >= m_maxDynamicContexts) + { + { + if (KateHlManager::self()->resetDynamicCtxs()) + { + kdDebug (13020) << "HL invalidated - too many dynamic contexts ( >= " << m_maxDynamicContexts << ")" << endl; + + // avoid recursive invalidation + KateHlManager::self()->setForceNoDCReset(true); + + for (KateDocument *doc = KateFactory::self()->documents()->first(); doc; doc = KateFactory::self()->documents()->next()) + doc->makeAttribs(); + + // doHighlight *shall* do his work. After invalidation, some highlight has + // been recalculated, but *maybe not* until endLine ! So we shall force it manually... + KateBufBlock *buf = 0; + while ((endLine > m_lineHighlighted) && (buf = findBlock(m_lineHighlighted))) + { + uint end = kMin(endLine, buf->endLine()); + + doHighlight ( buf, + kMax(m_lineHighlighted, buf->startLine()), + end, + false ); + + m_lineHighlighted = end; + } + + KateHlManager::self()->setForceNoDCReset(false); + + return false; + } + else + { + m_maxDynamicContexts *= 2; + kdDebug (13020) << "New dynamic contexts limit: " << m_maxDynamicContexts << endl; + } + } + } + + // get the previous line, if we start at the beginning of this block + // take the last line of the previous block + KateTextLine::Ptr prevLine = 0; + + if ((startLine == buf->startLine()) && buf->prev() && (buf->prev()->lines() > 0)) + prevLine = buf->prev()->line (buf->prev()->lines() - 1); + else if ((startLine > buf->startLine()) && (startLine <= buf->endLine())) + prevLine = buf->line(startLine - buf->startLine() - 1); + else + prevLine = new KateTextLine (); + + // does we need to emit a signal for the folding changes ? + bool codeFoldingUpdate = false; + + // here we are atm, start at start line in the block + uint current_line = startLine - buf->startLine(); + + // do we need to continue + bool stillcontinue=false; + bool indentContinueWhitespace=false; + bool indentContinueNextWhitespace=false; + // loop over the lines of the block, from startline to endline or end of block + // if stillcontinue forces us to do so + while ( (current_line < buf->lines()) + && (stillcontinue || ((current_line + buf->startLine()) <= endLine)) ) + { + // current line + KateTextLine::Ptr textLine = buf->line(current_line); + + QMemArray foldingList; + bool ctxChanged = false; + + m_highlight->doHighlight (prevLine, textLine, &foldingList, &ctxChanged); + + // + // indentation sensitive folding + // + bool indentChanged = false; + if (m_highlight->foldingIndentationSensitive()) + { + // get the indentation array of the previous line to start with ! + QMemArray indentDepth; + indentDepth.duplicate (prevLine->indentationDepthArray()); + + // current indentation of this line + uint iDepth = textLine->indentDepth(m_tabWidth); + if ((current_line+buf->startLine())==0) + { + indentDepth.resize (1, QGArray::SpeedOptim); + indentDepth[0] = iDepth; + } + + textLine->setNoIndentBasedFoldingAtStart(prevLine->noIndentBasedFolding()); + // this line is empty, beside spaces, or has indentaion based folding disabled, use indentation depth of the previous line ! + kdDebug(13020)<<"current_line:"<startLine()<<" textLine->noIndentBasedFoldingAtStart"<noIndentBasedFoldingAtStart()<firstChar() == -1) || textLine->noIndentBasedFoldingAtStart()) + { + // do this to get skipped empty lines indent right, which was given in the indenation array + if (!prevLine->indentationDepthArray().isEmpty()) + { + iDepth = (prevLine->indentationDepthArray())[prevLine->indentationDepthArray().size()-1]; + kdDebug(13020)<<"reusing old depth as current"<indentDepth(m_tabWidth); + kdDebug(13020)<<"creating indentdepth for previous line"<lines()) + { + if (buf->line(current_line+1)->firstChar() == -1) + { + nextLineIndentation = iDepth; + indentContinueNextWhitespace=true; + } + else + nextLineIndentation = buf->line(current_line+1)->indentDepth(m_tabWidth); + } + else + { + KateBufBlock *blk = buf->next(); + + if (blk && (blk->lines() > 0)) + { + if (blk->line (0)->firstChar() == -1) + { + nextLineIndentation = iDepth; + indentContinueNextWhitespace=true; + } + else + nextLineIndentation = blk->line (0)->indentDepth(m_tabWidth); + } + else nextLineIndentationValid=false; + } + + if (!textLine->noIndentBasedFoldingAtStart()) { + + if ((iDepth > 0) && (indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1] < iDepth))) + { + kdDebug(13020)<<"adding depth to \"stack\":"< -1; z--) + if (indentDepth[z]>iDepth) + indentDepth.resize(z, QGArray::SpeedOptim); + if ((iDepth > 0) && (indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1] < iDepth))) + { + kdDebug(13020)<<"adding depth to \"stack\":"<firstChar()==-1) { + + } + } + } + } + } + + if (!textLine->noIndentBasedFolding()) + { + if (nextLineIndentationValid) + { + //if (textLine->firstChar()!=-1) + { + kdDebug(13020)<<"nextLineIndentation:"<0) && ( indentDepth.isEmpty() || (indentDepth[indentDepth.size()-1]nextLineIndentation)) + { + kdDebug(13020)<<"...."< -1; z--) + { + kdDebug(13020)<nextLineIndentation) + deindent++; + } + } + } +/* } + if (textLine->noIndentBasedFolding()) kdDebug(13020)<<"=============================indentation based folding disabled======================"<noIndentBasedFolding()) {*/ + if ((textLine->firstChar()==-1)) { + updatePreviousNotEmptyLine(buf,current_line,addindent,deindent); + codeFoldingUpdate=true; + } + else + { + addIndentBasedFoldingInformation(foldingList,addindent,deindent); + } + } + } + } + indentChanged = !(indentDepth == textLine->indentationDepthArray()); + + // assign the new array to the textline ! + if (indentChanged) + textLine->setIndentationDepth (indentDepth); + + indentContinueWhitespace=textLine->firstChar()==-1; + } + bool foldingColChanged=false; + bool foldingChanged = false; //!(foldingList == textLine->foldingListArray()); + if (foldingList.size()!=textLine->foldingListArray().size()) { + foldingChanged=true; + } else { + QMemArray::ConstIterator it=foldingList.begin(); + QMemArray::ConstIterator it1=textLine->foldingListArray(); + bool markerType=true; + for(;it!=foldingList.end();++it,++it1) { + if (markerType) { + if ( ((*it)!=(*it1))) { + foldingChanged=true; + foldingColChanged=false; + break; + } + } else { + if ((*it)!=(*it1)) { + foldingColChanged=true; + } + } + markerType=!markerType; + } + } + + if (foldingChanged || foldingColChanged) { + textLine->setFoldingList(foldingList); + if (foldingChanged==false){ + textLine->setFoldingColumnsOutdated(textLine->foldingColumnsOutdated() | foldingColChanged); + } else textLine->setFoldingColumnsOutdated(false); + } + bool retVal_folding = false; + //perhaps make en enums out of the change flags + m_regionTree.updateLine (current_line + buf->startLine(), &foldingList, &retVal_folding, foldingChanged,foldingColChanged); + + codeFoldingUpdate = codeFoldingUpdate | retVal_folding; + + // need we to continue ? + stillcontinue = ctxChanged || indentChanged || indentContinueWhitespace || indentContinueNextWhitespace; + + // move around the lines + prevLine = textLine; + + // increment line + current_line++; + } + + buf->markDirty (); + + // tag the changed lines ! + if (invalidate) + emit tagLines (startLine, current_line + buf->startLine()); + + // emit that we have changed the folding + if (codeFoldingUpdate) + emit codeFoldingUpdated(); + + //kdDebug (13020) << "HIGHLIGHTED END --- NEED HL, LINESTART: " << startLine << " LINEEND: " << endLine << endl; + //kdDebug (13020) << "HL UNTIL LINE: " << m_lineHighlighted << " MAX: " << m_lineHighlightedMax << endl; + //kdDebug (13020) << "HL DYN COUNT: " << KateHlManager::self()->countDynamicCtxs() << " MAX: " << m_maxDynamicContexts << endl; + //kdDebug (13020) << "TIME TAKEN: " << t.elapsed() << endl; + + // if we are at the last line of the block + we still need to continue + // return the need of that ! + return stillcontinue && ((current_line+1) == buf->lines()); +} + +void KateBuffer::codeFoldingColumnUpdate(unsigned int lineNr) { + KateTextLine::Ptr line=plainLine(lineNr); + if (!line) return; + if (line->foldingColumnsOutdated()) { + line->setFoldingColumnsOutdated(false); + bool tmp; + QMemArray folding=line->foldingListArray(); + m_regionTree.updateLine(lineNr,&folding,&tmp,true,false); + } +} + +//BEGIN KateBufBlock + +KateBufBlock::KateBufBlock ( KateBuffer *parent, KateBufBlock *prev, KateBufBlock *next, + KateFileLoader *stream ) +: m_state (KateBufBlock::stateDirty), + m_startLine (0), + m_lines (0), + m_vmblock (0), + m_vmblockSize (0), + m_parent (parent), + m_prev (prev), + m_next (next), + list (0), + listPrev (0), + listNext (0) +{ + // init startline + the next pointers of the neighbour blocks + if (m_prev) + { + m_startLine = m_prev->endLine (); + m_prev->m_next = this; + } + + if (m_next) + m_next->m_prev = this; + + // we have a stream, use it to fill the block ! + // this can lead to 0 line blocks which are invalid ! + if (stream) + { + // this we lead to either dirty or swapped state + fillBlock (stream); + } + else // init the block if no stream given ! + { + // fill in one empty line ! + KateTextLine::Ptr textLine = new KateTextLine (); + m_stringList.push_back (textLine); + m_lines++; + + // if we have allready enough blocks around, swap one + if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) + m_parent->m_loadedBlocks.first()->swapOut(); + + // we are a new nearly empty dirty block + m_state = KateBufBlock::stateDirty; + m_parent->m_loadedBlocks.append (this); + } +} + +KateBufBlock::~KateBufBlock () +{ + // sync prev/next pointers + if (m_prev) + m_prev->m_next = m_next; + + if (m_next) + m_next->m_prev = m_prev; + + // if we have some swapped data allocated, free it now or never + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + // remove me from the list I belong + KateBufBlockList::remove (this); +} + +void KateBufBlock::fillBlock (KateFileLoader *stream) +{ + // is allready too much stuff around in mem ? + bool swap = m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks(); + + QByteArray rawData; + + // calcs the approx size for KATE_AVG_BLOCK_SIZE chars ! + if (swap) + rawData.resize ((KATE_AVG_BLOCK_SIZE * sizeof(QChar)) + ((KATE_AVG_BLOCK_SIZE/80) * 8)); + + char *buf = rawData.data (); + uint size = 0; + uint blockSize = 0; + while (!stream->eof() && (blockSize < KATE_AVG_BLOCK_SIZE) && (m_lines < KATE_MAX_BLOCK_LINES)) + { + uint offset = 0, length = 0; + stream->readLine(offset, length); + const QChar *unicodeData = stream->unicode () + offset; + + // strip spaces at end of line + if ( stream->removeTrailingSpaces() ) + { + while (length > 0) + { + if (unicodeData[length-1].isSpace()) + --length; + else + break; + } + } + + blockSize += length; + + if (swap) + { + // create the swapped data on the fly, no need to waste time + // via going over the textline classes and dump them ! + char attr = KateTextLine::flagNoOtherData; + uint pos = size; + + // calc new size + size = size + 1 + sizeof(uint) + (sizeof(QChar)*length); + + if (size > rawData.size ()) + { + rawData.resize (size); + buf = rawData.data (); + } + + memcpy(buf+pos, (char *) &attr, 1); + pos += 1; + + memcpy(buf+pos, (char *) &length, sizeof(uint)); + pos += sizeof(uint); + + memcpy(buf+pos, (char *) unicodeData, sizeof(QChar)*length); + pos += sizeof(QChar)*length; + } + else + { + KateTextLine::Ptr textLine = new KateTextLine (); + textLine->insertText (0, length, unicodeData); + m_stringList.push_back (textLine); + } + + m_lines++; + } + + if (swap) + { + m_vmblock = KateFactory::self()->vm()->allocate(size); + m_vmblockSize = size; + + if (!rawData.isEmpty()) + { + if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, size)) + { + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + m_parent->m_cacheWriteError = true; + } + } + + // fine, we are swapped ! + m_state = KateBufBlock::stateSwapped; + } + else + { + // we are a new dirty block without any swap data + m_state = KateBufBlock::stateDirty; + m_parent->m_loadedBlocks.append (this); + } + + kdDebug (13020) << "A BLOCK LOADED WITH LINES: " << m_lines << endl; +} + +KateTextLine::Ptr KateBufBlock::line(uint i) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + // LRU + if (!m_parent->m_loadedBlocks.isLast(this)) + m_parent->m_loadedBlocks.append (this); + + return m_stringList[i]; +} + +void KateBufBlock::insertLine(uint i, KateTextLine::Ptr line) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + m_stringList.insert (m_stringList.begin()+i, line); + m_lines++; + + markDirty (); +} + +void KateBufBlock::removeLine(uint i) +{ + // take care that the string list is around !!! + if (m_state == KateBufBlock::stateSwapped) + swapIn (); + + m_stringList.erase (m_stringList.begin()+i); + m_lines--; + + markDirty (); +} + +void KateBufBlock::markDirty () +{ + if (m_state != KateBufBlock::stateSwapped) + { + // LRU + if (!m_parent->m_loadedBlocks.isLast(this)) + m_parent->m_loadedBlocks.append (this); + + if (m_state == KateBufBlock::stateClean) + { + // if we have some swapped data allocated which is dirty, free it now + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + // we are dirty + m_state = KateBufBlock::stateDirty; + } + } +} + +void KateBufBlock::swapIn () +{ + if (m_state != KateBufBlock::stateSwapped) + return; + + QByteArray rawData (m_vmblockSize); + + // what to do if that fails ? + if (!KateFactory::self()->vm()->copyBlock(rawData.data(), m_vmblock, 0, rawData.size())) + m_parent->m_cacheReadError = true; + + // reserve mem, keep realloc away on push_back + m_stringList.reserve (m_lines); + + char *buf = rawData.data(); + for (uint i=0; i < m_lines; i++) + { + KateTextLine::Ptr textLine = new KateTextLine (); + buf = textLine->restore (buf); + m_stringList.push_back (textLine); + } + + // if we have allready enough blocks around, swap one + if (m_parent->m_loadedBlocks.count() >= KateBuffer::maxLoadedBlocks()) + m_parent->m_loadedBlocks.first()->swapOut(); + + // fine, we are now clean again, save state + append to clean list + m_state = KateBufBlock::stateClean; + m_parent->m_loadedBlocks.append (this); +} + +void KateBufBlock::swapOut () +{ + if (m_state == KateBufBlock::stateSwapped) + return; + + if (m_state == KateBufBlock::stateDirty) + { + bool haveHl = m_parent->m_highlight && !m_parent->m_highlight->noHighlighting(); + + // Calculate size. + uint size = 0; + for (uint i=0; i < m_lines; i++) + size += m_stringList[i]->dumpSize (haveHl); + + QByteArray rawData (size); + char *buf = rawData.data(); + + // Dump textlines + for (uint i=0; i < m_lines; i++) + buf = m_stringList[i]->dump (buf, haveHl); + + m_vmblock = KateFactory::self()->vm()->allocate(rawData.size()); + m_vmblockSize = rawData.size(); + + if (!rawData.isEmpty()) + { + if (!KateFactory::self()->vm()->copyBlock(m_vmblock, rawData.data(), 0, rawData.size())) + { + if (m_vmblock) + KateFactory::self()->vm()->free(m_vmblock); + + m_vmblock = 0; + m_vmblockSize = 0; + + m_parent->m_cacheWriteError = true; + + return; + } + } + } + + m_stringList.clear(); + + // we are now swapped out, set state + remove us out of the lists ! + m_state = KateBufBlock::stateSwapped; + KateBufBlockList::remove (this); +} + +//END KateBufBlock + +//BEGIN KateBufBlockList + +KateBufBlockList::KateBufBlockList () + : m_count (0), + m_first (0), + m_last (0) +{ +} + +void KateBufBlockList::append (KateBufBlock *buf) +{ + if (buf->list) + buf->list->removeInternal (buf); + + m_count++; + + // append a element + if (m_last) + { + m_last->listNext = buf; + + buf->listPrev = m_last; + buf->listNext = 0; + + m_last = buf; + + buf->list = this; + + return; + } + + // insert the first element + m_last = buf; + m_first = buf; + + buf->listPrev = 0; + buf->listNext = 0; + + buf->list = this; +} + +void KateBufBlockList::removeInternal (KateBufBlock *buf) +{ + if (buf->list != this) + return; + + m_count--; + + if ((buf == m_first) && (buf == m_last)) + { + // last element removed ! + m_first = 0; + m_last = 0; + } + else if (buf == m_first) + { + // first element removed + m_first = buf->listNext; + m_first->listPrev = 0; + } + else if (buf == m_last) + { + // last element removed + m_last = buf->listPrev; + m_last->listNext = 0; + } + else + { + buf->listPrev->listNext = buf->listNext; + buf->listNext->listPrev = buf->listPrev; + } + + buf->listPrev = 0; + buf->listNext = 0; + + buf->list = 0; +} + +//END KateBufBlockList + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katebuffer.h b/kate/part/katebuffer.h new file mode 100644 index 000000000..6b2d48ead --- /dev/null +++ b/kate/part/katebuffer.h @@ -0,0 +1,709 @@ +/* This file is part of the KDE libraries + Copyright (c) 2000 Waldo Bastian + Copyright (C) 2002-2004 Christoph Cullmann + + 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 02110-1301, USA. +*/ + +#ifndef __KATE_BUFFER_H__ +#define __KATE_BUFFER_H__ + +#include "katetextline.h" +#include "katecodefoldinghelpers.h" + +#include + +#include +#include +#include +#include + +class KateLineInfo; +class KateDocument; +class KateHighlighting; +class KateBufBlockList; +class KateBuffer; +class KateFileLoader; + +class QTextCodec; + +/** + * The KateBufBlock class contains an amount of data representing + * a certain number of lines. + * + * @author Waldo Bastian + * @author Christoph Cullmann + */ +class KateBufBlock +{ + friend class KateBufBlockList; + + public: + /** + * Create an empty block. (empty == ONE line) + * @param parent buffer the block belongs to + * @param prev previous bufblock in the list + * @param next next bufblock in the list + * @param stream stream to load the content from, if any given + */ + KateBufBlock ( KateBuffer *parent, KateBufBlock *prev = 0, KateBufBlock *next = 0, + KateFileLoader *stream = 0 ); + + /** + * destroy this block and take care of freeing all mem + */ + ~KateBufBlock (); + + private: + /** + * fill the block with the lines from the given stream + * @param stream stream to load data from + */ + void fillBlock (KateFileLoader *stream); + + public: + /** + * state flags + */ + enum State + { + stateSwapped = 0, + stateClean = 1, + stateDirty = 2 + }; + + /** + * returns the current state of this block + * @return state + */ + State state () const { return m_state; } + + public: + /** + * return line @p i + * The first line of this block is line 0. + * if you modifiy this line, please mark the block as dirty + * @param i line to return + * @return line pointer + */ + KateTextLine::Ptr line(uint i); + + /** + * insert @p line in front of line @p i + * marks the block dirty + * @param i where to insert + * @param line line pointer + */ + void insertLine(uint i, KateTextLine::Ptr line); + + /** + * remove line @p i + * marks the block dirty + * @param i line to remove + */ + void removeLine(uint i); + + /** + * mark this block as dirty, will invalidate the swap data + * insert/removeLine will mark the block dirty itself + */ + void markDirty (); + + public: + /** + * startLine + * @return first line in block + */ + inline uint startLine () const { return m_startLine; }; + + /** + * update the first line, needed to keep it up to date + * @param line new startLine + */ + inline void setStartLine (uint line) { m_startLine = line; } + + /** + * first line behind this block + * @return line behind block + */ + inline uint endLine () const { return m_startLine + m_lines; } + + /** + * lines in this block + * @return lines + */ + inline uint lines () const { return m_lines; } + + /** + * prev block + * @return previous block + */ + inline KateBufBlock *prev () { return m_prev; } + + /** + * next block + * @return next block + */ + inline KateBufBlock *next () { return m_next; } + + /** + * methodes to swap in/out + */ + private: + /** + * swap in the kvmallocater data, create string list + */ + void swapIn (); + + /** + * swap our string list out, delete it ! + */ + void swapOut (); + + private: + /** + * VERY IMPORTANT, state of this block + * this uchar indicates if the block is swapped, loaded, clean or dirty + */ + KateBufBlock::State m_state; + + /** + * IMPORTANT, start line + */ + uint m_startLine; + + /** + * IMPORTANT, line count + */ + uint m_lines; + + /** + * here we swap our stuff + */ + KVMAllocator::Block *m_vmblock; + + /** + * swapped size + */ + uint m_vmblockSize; + + /** + * list of textlines + */ + QValueVector m_stringList; + + /** + * parent buffer. + */ + KateBuffer* m_parent; + + /** + * prev block + */ + KateBufBlock *m_prev; + + /** + * next block + */ + KateBufBlock *m_next; + + private: + /** + * list pointer, to which list I belong + * list element pointers for the KateBufBlockList ONLY !!! + */ + KateBufBlockList *list; + + /** + * prev list item + */ + KateBufBlock *listPrev; + + /** + * next list item + */ + KateBufBlock *listNext; +}; + +/** + * list which allows O(1) inserts/removes + * will not delete the elements on remove + * will use the next/prevNode pointers in the KateBufBlocks ! + * internal use: loaded/clean/dirty block lists + * + * @author Christoph Cullmann + */ +class KateBufBlockList +{ + public: + /** + * Default Constructor + */ + KateBufBlockList (); + + public: + /** + * count of blocks in this list + * @return count of blocks + */ + inline uint count() const { return m_count; } + + /** + * first block in this list or 0 + * @return head of list + */ + inline KateBufBlock *first () { return m_first; }; + + /** + * last block in this list or 0 + * @return end of list + */ + inline KateBufBlock *last () { return m_last; }; + + /** + * is buf the last block? + * @param buf block to test + * @return is this block the first one? + */ + inline bool isFirst (KateBufBlock *buf) { return m_first == buf; }; + + /** + * is buf the last block? + * @param buf block to test + * @return is this block the last one? + */ + inline bool isLast (KateBufBlock *buf) { return m_last == buf; }; + + /** + * append a block to this list ! + * will remove it from the list it belonged before ! + * @param buf block to append + */ + void append (KateBufBlock *buf); + + /** + * remove the block from the list it belongs to ! + * @param buf block to remove + */ + inline static void remove (KateBufBlock *buf) + { + if (buf->list) + buf->list->removeInternal (buf); + } + + private: + /** + * internal helper for remove + * @param buf block to remove + */ + void removeInternal (KateBufBlock *buf); + + private: + /** + * count of blocks in list + */ + uint m_count; + + /** + * first block + */ + KateBufBlock *m_first; + + /** + * last block + */ + KateBufBlock *m_last; +}; + +/** + * The KateBuffer class maintains a collections of lines. + * It allows to maintain state information in a lazy way. + * It handles swapping out of data using secondary storage. + * + * It is designed to handle large amounts of text-data efficiently + * with respect to CPU and memory usage. + * + * @author Waldo Bastian + * @author Christoph Cullmann + */ +class KateBuffer : public QObject +{ + Q_OBJECT + + friend class KateBufBlock; + + public: + /** + * maximal loaded block count + * @return max loaded blocks + */ + inline static uint maxLoadedBlocks () { return m_maxLoadedBlocks; } + + /** + * modifier for max loaded blocks limit + * @param count new limit + */ + static void setMaxLoadedBlocks (uint count); + + private: + /** + * global max loaded blocks limit + */ + static uint m_maxLoadedBlocks; + + public: + /** + * Create an empty buffer. + * @param doc parent document + */ + KateBuffer (KateDocument *doc); + + /** + * Goodbye buffer + */ + ~KateBuffer (); + + public: + /** + * start some editing action + */ + void editStart (); + + /** + * finish some editing action + */ + void editEnd (); + + /** + * were there changes in the current running + * editing session? + * @return changes done? + */ + inline bool editChanged () const { return editChangesDone; } + + /** + * dirty lines start + * @return start line + */ + inline uint editTagStart () const { return editTagLineStart; } + + /** + * dirty lines end + * @return end line + */ + inline uint editTagEnd () const { return editTagLineEnd; } + + /** + * line inserted/removed? + * @return line inserted/removed? + */ + inline bool editTagFrom () const { return editTagLineFrom; } + + private: + /** + * edit session recursion + */ + uint editSessionNumber; + + /** + * is a edit session running + */ + bool editIsRunning; + + /** + * dirty lines start at line + */ + uint editTagLineStart; + + /** + * dirty lines end at line + */ + uint editTagLineEnd; + + /** + * a line was inserted or removed + */ + bool editTagLineFrom; + + /** + * changes done? + */ + bool editChangesDone; + + public: + /** + * Clear the buffer. + */ + void clear(); + + /** + * Open a file, use the given filename + * @param m_file filename to open + * @return success + */ + bool openFile (const QString &m_file); + + /** + * was the last loading broken because of not enough tmp disk space ? + * (will be reseted on successful save of the file, user gets warning if he really wants to do it) + * @return was loading borked? + */ + bool loadingBorked () const { return m_loadingBorked; } + + /** + * is this file a binary? + * @return binary file? + */ + bool binary () const { return m_binary; } + + /** + * Can the current codec handle all chars + * @return chars can be encoded + */ + bool canEncode (); + + /** + * Save the buffer to a file, use the given filename + codec + end of line chars (internal use of qtextstream) + * @param m_file filename to save to + * @return success + */ + bool saveFile (const QString &m_file); + + public: + /** + * Return line @p i + */ + inline KateTextLine::Ptr line(uint i) + { + KateBufBlock *buf = findBlock(i); + if (!buf) + return 0; + + if (i < m_lineHighlighted) + return buf->line (i - buf->startLine()); + + return line_internal (buf, i); + } + + private: + /** + * line needs hl + */ + KateTextLine::Ptr line_internal (KateBufBlock *buf, uint i); + + inline void addIndentBasedFoldingInformation(QMemArray &foldingList,bool addindent,uint deindent); + inline void updatePreviousNotEmptyLine(KateBufBlock *blk,uint current_line,bool addindent,uint deindent); + public: + /** + * Return line @p i without triggering highlighting + */ + inline KateTextLine::Ptr plainLine(uint i) + { + KateBufBlock *buf = findBlock(i); + if (!buf) + return 0; + + return buf->line(i - buf->startLine()); + } + + /** + * Return the total number of lines in the buffer. + */ + inline uint count() const { return m_lines; } + + private: + /** + * Find the block containing line @p i + * index pointer gets filled with index of block in m_blocks + * index only valid if returned block != 0 ! + */ + KateBufBlock *findBlock (uint i, uint *index = 0) + { + // out of range ! + if (i >= m_lines) + return 0; + + if ((m_blocks[m_lastFoundBlock]->startLine() <= i) && (m_blocks[m_lastFoundBlock]->endLine() > i)) + { + if (index) + (*index) = m_lastFoundBlock; + + return m_blocks[m_lastFoundBlock]; + } + + return findBlock_internal (i, index); + } + + KateBufBlock *findBlock_internal (uint i, uint *index = 0); + + public: + /** + * Mark line @p i as changed ! + */ + void changeLine(uint i); + + /** + * Insert @p line in front of line @p i + */ + void insertLine(uint i, KateTextLine::Ptr line); + + /** + * Remove line @p i + */ + void removeLine(uint i); + + public: + inline uint countVisible () { return m_lines - m_regionTree.getHiddenLinesCount(m_lines); } + + inline uint lineNumber (uint visibleLine) { return m_regionTree.getRealLine (visibleLine); } + + inline uint lineVisibleNumber (uint line) { return m_regionTree.getVirtualLine (line); } + + inline void lineInfo (KateLineInfo *info, unsigned int line) { m_regionTree.getLineInfo(info,line); } + + inline uint tabWidth () const { return m_tabWidth; } + + public: + void setTabWidth (uint w); + + /** + * Use @p highlight for highlighting + * + * @p highlight may be 0 in which case highlighting + * will be disabled. + */ + void setHighlight (uint hlMode); + + KateHighlighting *highlight () { return m_highlight; }; + + /** + * Invalidate highlighting of whole buffer. + */ + void invalidateHighlighting(); + + KateCodeFoldingTree *foldingTree () { return &m_regionTree; }; + + public slots: + void codeFoldingColumnUpdate(unsigned int lineNr); + + private: + /** + * Highlight information needs to be updated. + * + * @param buf The buffer being processed. + * @param startState highlighting state of last line before range + * @param from first line in range + * @param to last line in range + * @param invalidat should the rehighlighted lines be tagged ? + * + * @returns true when the highlighting in the next block needs to be updated, + * false otherwise. + */ + bool doHighlight (KateBufBlock *buf, uint from, uint to, bool invalidate); + + signals: + /** + * Emittend if codefolding returned with a changed list + */ + void codeFoldingUpdated(); + + /** + * Emitted when the highlighting of a certain range has + * changed. + */ + void tagLines(int start, int end); + + private: + /** + * document we belong too + */ + KateDocument *m_doc; + + /** + * current line count + */ + uint m_lines; + + /** + * ALL blocks + * in order of linenumbers + */ + QValueVector m_blocks; + + /** + * last block where the start/end line is in sync with real life + */ + uint m_lastInSyncBlock; + + /** + * last block found by findBlock, there to make searching faster + */ + uint m_lastFoundBlock; + + /** + * status of the cache read/write errors + * write errors get handled, read errors not really atm + */ + bool m_cacheReadError; + bool m_cacheWriteError; + + /** + * had we cache error while loading ? + */ + bool m_loadingBorked; + + /** + * binary file loaded ? + */ + bool m_binary; + + /** + * highlighting & folding relevant stuff + */ + private: + /** + * current highlighting mode or 0 + */ + KateHighlighting *m_highlight; + + /** + * folding tree + */ + KateCodeFoldingTree m_regionTree; + + // for the scrapty indent sensitive langs + uint m_tabWidth; + + uint m_lineHighlightedMax; + uint m_lineHighlighted; + + /** + * number of dynamic contexts causing a full invalidation + */ + uint m_maxDynamicContexts; + + /** + * only used from the KateBufBlocks ! + */ + private: + /** + * all not swapped blocks ! + */ + KateBufBlockList m_loadedBlocks; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecmds.cpp b/kate/part/katecmds.cpp new file mode 100644 index 000000000..17846dd7d --- /dev/null +++ b/kate/part/katecmds.cpp @@ -0,0 +1,605 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 - 2005 Anders Lund + Copyright (C) 2001-2004 Christoph Cullmann + Copyright (C) 2001 Charles Samuels + + 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 02110-1301, USA. +*/ + +#include "katecmds.h" + +#include "katedocument.h" +#include "kateview.h" +#include "kateconfig.h" +#include "kateautoindent.h" +#include "katetextline.h" +#include "katefactory.h" +#include "katejscript.h" +#include "katerenderer.h" + +#include "../interfaces/katecmd.h" + +#include +#include +#include +#include + +#include + + +//BEGIN CoreCommands +// syncs a config flag in the document with a boolean value +static void setDocFlag( KateDocumentConfig::ConfigFlags flag, bool enable, + KateDocument *doc ) +{ + doc->config()->setConfigFlags( flag, enable ); +} + +// this returns wheather the string s could be converted to +// a bool value, one of on|off|1|0|true|false. the argument val is +// set to the extracted value in case of success +static bool getBoolArg( QString s, bool *val ) +{ + bool res( false ); + s = s.lower(); + res = (s == "on" || s == "1" || s == "true"); + if ( res ) + { + *val = true; + return true; + } + res = (s == "off" || s == "0" || s == "false"); + if ( res ) + { + *val = false; + return true; + } + return false; +} + +QStringList KateCommands::CoreCommands::cmds() +{ + QStringList l; + l << "indent" << "unindent" << "cleanindent" + << "comment" << "uncomment" << "goto" << "kill-line" + << "set-tab-width" << "set-replace-tabs" << "set-show-tabs" + << "set-remove-trailing-space" + << "set-indent-spaces" << "set-indent-width" << "set-mixed-indent" + << "set-indent-mode" << "set-auto-indent" + << "set-line-numbers" << "set-folding-markers" << "set-icon-border" + << "set-wrap-cursor" + << "set-word-wrap" << "set-word-wrap-column" + << "set-replace-tabs-save" << "set-remove-trailing-space-save" + << "set-highlight" << "run-myself" << "set-show-indent"; + return l; +} + +bool KateCommands::CoreCommands::exec(Kate::View *view, + const QString &_cmd, + QString &errorMsg) +{ +#define KCC_ERR(s) { errorMsg=s; return false; } + // cast it hardcore, we know that it is really a kateview :) + KateView *v = (KateView*) view; + + if ( ! v ) + KCC_ERR( i18n("Could not access view") ); + + //create a list of args + QStringList args( QStringList::split( QRegExp("\\s+"), _cmd ) ); + QString cmd ( args.first() ); + args.remove( args.first() ); + + // ALL commands that takes no arguments. + if ( cmd == "indent" ) + { + v->indent(); + return true; + } + else if ( cmd == "run-myself" ) + { +#ifndef Q_WS_WIN //todo + return KateFactory::self()->jscript()->execute(v, v->doc()->text(), errorMsg); +#else + return 0; +#endif + } + else if ( cmd == "unindent" ) + { + v->unIndent(); + return true; + } + else if ( cmd == "cleanindent" ) + { + v->cleanIndent(); + return true; + } + else if ( cmd == "comment" ) + { + v->comment(); + return true; + } + else if ( cmd == "uncomment" ) + { + v->uncomment(); + return true; + } + else if ( cmd == "kill-line" ) + { + v->killLine(); + return true; + } + else if ( cmd == "set-indent-mode" ) + { + bool ok(false); + int val ( args.first().toInt( &ok ) ); + if ( ok ) + { + if ( val < 0 ) + KCC_ERR( i18n("Mode must be at least 0.") ); + v->doc()->config()->setIndentationMode( val ); + } + else + v->doc()->config()->setIndentationMode( KateAutoIndent::modeNumber( args.first() ) ); + return true; + } + else if ( cmd == "set-highlight" ) + { + QString val = _cmd.section( ' ', 1 ).lower(); + for ( uint i=0; i < v->doc()->hlModeCount(); i++ ) + { + if ( v->doc()->hlModeName( i ).lower() == val ) + { + v->doc()->setHlMode( i ); + return true; + } + } + KCC_ERR( i18n("No such highlight '%1'").arg( args.first() ) ); + } + + // ALL commands that takes exactly one integer argument. + else if ( cmd == "set-tab-width" || + cmd == "set-indent-width" || + cmd == "set-word-wrap-column" || + cmd == "goto" ) + { + // find a integer value > 0 + if ( ! args.count() ) + KCC_ERR( i18n("Missing argument. Usage: %1 ").arg( cmd ) ); + bool ok; + int val ( args.first().toInt( &ok ) ); + if ( !ok ) + KCC_ERR( i18n("Failed to convert argument '%1' to integer.") + .arg( args.first() ) ); + + if ( cmd == "set-tab-width" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Width must be at least 1.") ); + v->setTabWidth( val ); + } + else if ( cmd == "set-indent-width" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Width must be at least 1.") ); + v->doc()->config()->setIndentationWidth( val ); + } + else if ( cmd == "set-word-wrap-column" ) + { + if ( val < 2 ) + KCC_ERR( i18n("Column must be at least 1.") ); + v->doc()->setWordWrapAt( val ); + } + else if ( cmd == "goto" ) + { + if ( val < 1 ) + KCC_ERR( i18n("Line must be at least 1") ); + if ( (uint)val > v->doc()->numLines() ) + KCC_ERR( i18n("There is not that many lines in this document") ); + v->gotoLineNumber( val - 1 ); + } + return true; + } + + // ALL commands that takes 1 boolean argument. + else if ( cmd == "set-icon-border" || + cmd == "set-folding-markers" || + cmd == "set-line-numbers" || + cmd == "set-replace-tabs" || + cmd == "set-remove-trailing-space" || + cmd == "set-show-tabs" || + cmd == "set-indent-spaces" || + cmd == "set-mixed-indent" || + cmd == "set-word-wrap" || + cmd == "set-wrap-cursor" || + cmd == "set-replace-tabs-save" || + cmd == "set-remove-trailing-space-save" || + cmd == "set-show-indent" ) + { + if ( ! args.count() ) + KCC_ERR( i18n("Usage: %1 on|off|1|0|true|false").arg( cmd ) ); + bool enable; + if ( getBoolArg( args.first(), &enable ) ) + { + if ( cmd == "set-icon-border" ) + v->setIconBorder( enable ); + else if (cmd == "set-folding-markers") + v->setFoldingMarkersOn( enable ); + else if ( cmd == "set-line-numbers" ) + v->setLineNumbersOn( enable ); + else if ( cmd == "set-show-indent" ) + v->renderer()->setShowIndentLines( enable ); + else if ( cmd == "set-replace-tabs" ) + setDocFlag( KateDocumentConfig::cfReplaceTabsDyn, enable, v->doc() ); + else if ( cmd == "set-remove-trailing-space" ) + setDocFlag( KateDocumentConfig::cfRemoveTrailingDyn, enable, v->doc() ); + else if ( cmd == "set-show-tabs" ) + setDocFlag( KateDocumentConfig::cfShowTabs, enable, v->doc() ); + else if ( cmd == "set-indent-spaces" ) + setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); + else if ( cmd == "set-mixed-indent" ) + { + // this is special, in that everything is set up -- space-indent is enabled, + // and a indent-width is set if it is 0 (to tabwidth/2) + setDocFlag( KateDocumentConfig::cfMixedIndent, enable, v->doc() ); + if ( enable ) + { + setDocFlag( KateDocumentConfig::cfSpaceIndent, enable, v->doc() ); + if ( ! v->doc()->config()->indentationWidth() ) + v->doc()->config()->setIndentationWidth( v->tabWidth()/2 ); + } + } + else if ( cmd == "set-word-wrap" ) + v->doc()->setWordWrap( enable ); + else if ( cmd == "set-remove-trailing-space-save" ) + setDocFlag( KateDocumentConfig::cfRemoveSpaces, enable, v->doc() ); + else if ( cmd == "set-wrap-cursor" ) + setDocFlag( KateDocumentConfig::cfWrapCursor, enable, v->doc() ); + + return true; + } + else + KCC_ERR( i18n("Bad argument '%1'. Usage: %2 on|off|1|0|true|false") + .arg( args.first() ).arg( cmd ) ); + } + + // unlikely.. + KCC_ERR( i18n("Unknown command '%1'").arg(cmd) ); +} + +KCompletion *KateCommands::CoreCommands::completionObject( const QString &cmd, Kate::View *view ) +{ + if ( cmd == "set-highlight" ) + { + KateView *v = (KateView*)view; + QStringList l; + for ( uint i = 0; i < v->doc()->hlModeCount(); i++ ) + l << v->doc()->hlModeName( i ); + + KateCmdShellCompletion *co = new KateCmdShellCompletion(); + co->setItems( l ); + co->setIgnoreCase( true ); + return co; + } + return 0L; +} +//END CoreCommands + +//BEGIN SedReplace +static void replace(QString &s, const QString &needle, const QString &with) +{ + int pos=0; + while (1) + { + pos=s.find(needle, pos); + if (pos==-1) break; + s.replace(pos, needle.length(), with); + pos+=with.length(); + } + +} + +static int backslashString(const QString &haystack, const QString &needle, int index) +{ + int len=haystack.length(); + int searchlen=needle.length(); + bool evenCount=true; + while (indexkateTextLine( line ); + if ( ! ln || ! ln->length() ) return 0; + + // HANDLING "\n"s in PATTERN + // * Create a list of patterns, splitting PATTERN on (unescaped) "\n" + // * insert $s and ^s to match line ends/beginnings + // * When matching patterhs after the first one, replace \N with the captured + // text. + // * If all patterns in the list match sequentiel lines, there is a match, so + // * remove line/start to line + patterns.count()-1/patterns.last.length + // * handle capatures by putting them in one list. + // * the existing insertion is fine, including the line calculation. + + QStringList patterns = QStringList::split( QRegExp("(^\\\\n|(?![^\\\\])\\\\n)"), find, true ); + + if ( patterns.count() > 1 ) + { + for ( uint i = 0; i < patterns.count(); i++ ) + { + if ( i < patterns.count() - 1 ) + patterns[i].append("$"); + if ( i ) + patterns[i].prepend("^"); + + kdDebug(13025)<<"patterns["<searchText( startcol, matcher, &startcol, &len ) ) + { + + if ( endcol >= 0 && startcol + len > (uint)endcol ) + break; + + matches++; + + + QString rep=repOld; + + // now set the backreferences in the replacement + QStringList backrefs=matcher.capturedTexts(); + int refnum=1; + + QStringList::Iterator i = backrefs.begin(); + ++i; + + for (; i!=backrefs.end(); ++i) + { + // I need to match "\\" or "", but not "\" + QString number=QString::number(refnum); + + int index=0; + while (index!=-1) + { + index=backslashString(rep, number, index); + if (index>=0) + { + rep.replace(index, 2, *i); + index+=(*i).length(); + } + } + + refnum++; + } + + replace(rep, "\\\\", "\\"); + replace(rep, "\\" + delim, delim); + + doc->removeText( line, startcol, line, startcol + len ); + doc->insertText( line, startcol, rep ); + + // TODO if replace contains \n, + // change the line number and + // check for text that needs be searched behind the last inserted newline. + int lns = rep.contains('\n'); + if ( lns ) + { + line += lns; + + if ( doc->lineLength( line ) > 0 && ( endcol < 0 || (uint)endcol >= startcol + len ) ) + { + // if ( endcol >= startcol + len ) + endcol -= (startcol + len); + uint sc = rep.length() - rep.findRev('\n') - 1; + matches += sedMagic( doc, line, find, repOld, delim, noCase, repeat, sc, endcol ); + } + } + + if (!repeat) break; + startcol+=rep.length(); + + // sanity check -- avoid infinite loops eg with %s,.*,,g ;) + uint ll = ln->length(); + if ( ! ll || startcol > ll ) + break; + } + + return matches; +} + +bool KateCommands::SedReplace::exec (Kate::View *view, const QString &cmd, QString &msg) +{ + kdDebug(13025)<<"SedReplace::execCmd( "<doc(); + if ( ! doc ) return false; + + doc->editStart(); + + int res = 0; + + if (fullFile) + { + uint numLines=doc->numLines(); + for (int line=0; (uint)line < numLines; line++) + { + res += sedMagic( doc, line, find, replace, d, !noCase, repeat ); + if ( ! repeat && res ) break; + } + } + else if (onlySelect) + { + int startline = doc->selStartLine(); + uint startcol = doc->selStartCol(); + int endcol = -1; + do { + if ( startline == doc->selEndLine() ) + endcol = doc->selEndCol(); + + res += sedMagic( doc, startline, find, replace, d, !noCase, repeat, startcol, endcol ); + + /*if ( startcol )*/ startcol = 0; + + startline++; + } while ( (int)startline <= doc->selEndLine() ); + } + else // just this line + { + int line=view->cursorLine(); + res += sedMagic(doc, line, find, replace, d, !noCase, repeat); + } + + msg = i18n("1 replacement done", "%n replacements done",res ); + + doc->editEnd(); + + return true; +} +//END SedReplace + +//BEGIN Character +bool KateCommands::Character::exec (Kate::View *view, const QString &_cmd, QString &) +{ + QString cmd = _cmd; + + // hex, octal, base 9+1 + QRegExp num("^char *(0?x[0-9A-Fa-f]{1,4}|0[0-7]{1,6}|[0-9]{1,3})$"); + if (num.search(cmd)==-1) return false; + + cmd=num.cap(1); + + // identify the base + + unsigned short int number=0; + int base=10; + if (cmd[0]=='x' || cmd.left(2)=="0x") + { + cmd.replace(QRegExp("^0?x"), ""); + base=16; + } + else if (cmd[0]=='0') + base=8; + bool ok; + number=cmd.toUShort(&ok, base); + if (!ok || number==0) return false; + if (number<=255) + { + char buf[2]; + buf[0]=(char)number; + buf[1]=0; + view->insertText(QString(buf)); + } + else + { // do the unicode thing + QChar c(number); + view->insertText(QString(&c, 1)); + } + + return true; +} +//END Character + +//BEGIN Date +bool KateCommands::Date::exec (Kate::View *view, const QString &cmd, QString &) +{ + if (cmd.left(4) != "date") + return false; + + if (QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5)).length() > 0) + view->insertText(QDateTime::currentDateTime().toString(cmd.mid(5, cmd.length()-5))); + else + view->insertText(QDateTime::currentDateTime().toString("yyyy-MM-dd hh:mm:ss")); + + return true; +} +//END Date + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecmds.h b/kate/part/katecmds.h new file mode 100644 index 000000000..84f7919d8 --- /dev/null +++ b/kate/part/katecmds.h @@ -0,0 +1,178 @@ +/* This file is part of the KDE libraries + Copyright (C) 2003 Anders Lund + Copyright (C) 2001-2004 Christoph Cullmann + Copyright (C) 2001 Charles Samuels + + 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 02110-1301, USA. +*/ + +#ifndef __KATE_CMDS_H__ +#define __KATE_CMDS_H__ + +#include "../interfaces/document.h" +#include "../interfaces/view.h" + +class KateDocument; +class KCompletion; + +namespace KateCommands +{ + +/** + * This Kate::Command provides access to a lot of the core functionality + * of kate part, settings, utilities, navigation etc. + * it needs to get a kateview pointer, it will cast the kate::view pointer + * hard to kateview + */ +class CoreCommands : public Kate::Command, public Kate::CommandExtension +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec( class Kate::View *view, const QString &cmd, QString &errorMsg ); + + bool help( class Kate::View *, const QString &, QString & ) {return false;}; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds(); + + /** + * override completionObject from interfaces/document.h . + */ + KCompletion *completionObject( const QString &cmd, Kate::View *view ); +}; + +/** + * -- Charles Samuels + * Support vim/sed find and replace + * s/search/replace/ find search, replace with replace on this line + * %s/search/replace/ do the same to the whole file + * s/search/replace/i do the S. and R., but case insensitively + * $s/search/replace/ do the search are replacement to the selection only + * + * $s/// is currently unsupported + **/ +class SedReplace : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { QStringList l("s"); l << "%s" << "$s"; return l; }; + + private: + /** + * Searches one line and does the replacement in the document. + * If @p replace contains any newline characters, the reamaining part of the + * line is searched, and the @p line set to the last line number searched. + * @return the number of replacements performed. + * @param doc a pointer to the document to work on + * @param line the number of the line to search. This may be changed by the + * function, if newlines are inserted. + * @param find A regular expression pattern to use for searching + * @param replace a template for replacement. Backspaced integers are + * replaced with captured texts from the regular expression. + * @param delim the delimiter character from the command. In the replacement + * text backsplashes preceeding this character are removed. + * @param nocase parameter for matching the reqular expression. + * @param repeat If false, the search is stopped after the first match. + * @param startcol The position in the line to start the search. + * @param endcol The last collumn in the line allowed in a match. + * If it is -1, the whole line is used. + */ + static int sedMagic(KateDocument *doc, int &line, + const QString &find, const QString &replace, const QString &delim, + bool noCase, bool repeat, + uint startcol=0, int endcol=-1); +}; + +/** + * insert a unicode or ascii character + * base 9+1: 1234 + * hex: 0x1234 or x1234 + * octal: 01231 + * + * prefixed with "char:" + **/ +class Character : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { return QStringList("char"); }; +}; + +/** + * insert the current date/time in the given format + */ +class Date : public Kate::Command +{ + public: + /** + * execute command + * @param view view to use for execution + * @param cmd cmd string + * @param errorMsg error to return if no success + * @return success + */ + bool exec (class Kate::View *view, const QString &cmd, QString &errorMsg); + + bool help (class Kate::View *, const QString &, QString &) { return false; }; + + /** + * supported commands as prefixes + * @return prefix list + */ + QStringList cmds () { return QStringList("date"); }; +}; + + +} // namespace KateCommands +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodecompletion.cpp b/kate/part/katecodecompletion.cpp new file mode 100644 index 000000000..bbc34dfca --- /dev/null +++ b/kate/part/katecodecompletion.cpp @@ -0,0 +1,566 @@ +/* This file is part of the KDE libraries + Copyright (C) 2001 Joseph Wenninger + Copyright (C) 2002 John Firebaugh + Copyright (C) 2001 by Victor Röder + Copyright (C) 2002 by Roberto Raggi + + 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 02110-1301, USA. +*/ + +/******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ +/* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much + * left from the desigener code */ + +#include "katecodecompletion.h" +#include "katecodecompletion.moc" + +#include "katedocument.h" +#include "kateview.h" +#include "katerenderer.h" +#include "kateconfig.h" +#include "katefont.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +/** + * This class is used as the codecompletion listbox. It can be resized according to its contents, + * therfor the needed size is provided by sizeHint(); + *@short Listbox showing codecompletion + *@author Jonas B. Jacobi + */ +class KateCCListBox : public QListBox +{ + public: + /** + @short Create a new CCListBox + */ + KateCCListBox (QWidget* parent = 0, const char* name = 0, WFlags f = 0):QListBox(parent, name, f) + { + } + + QSize sizeHint() const + { + int count = this->count(); + int height = 20; + int tmpwidth = 8; + //FIXME the height is for some reasons at least 3 items heigh, even if there is only one item in the list + if (count > 0) + if(count < 11) + height = count * itemHeight(0); + else { + height = 10 * itemHeight(0); + tmpwidth += verticalScrollBar()->width(); + } + + int maxcount = 0, tmpcount = 0; + for (int i = 0; i < count; ++i) + if ( (tmpcount = fontMetrics().width(text(i)) ) > maxcount) + maxcount = tmpcount; + + if (maxcount > QApplication::desktop()->width()){ + tmpwidth = QApplication::desktop()->width() - 5; + height += horizontalScrollBar()->height(); + } else + tmpwidth += maxcount; + return QSize(tmpwidth,height); + + } +}; + +class KateCompletionItem : public QListBoxText +{ + public: + KateCompletionItem( QListBox* lb, KTextEditor::CompletionEntry entry ) + : QListBoxText( lb ) + , m_entry( entry ) + { + if( entry.postfix == "()" ) { // should be configurable + setText( entry.prefix + " " + entry.text + entry.postfix ); + } else { + setText( entry.prefix + " " + entry.text + " " + entry.postfix); + } + } + + KTextEditor::CompletionEntry m_entry; +}; + + +KateCodeCompletion::KateCodeCompletion( KateView* view ) + : QObject( view, "Kate Code Completion" ) + , m_view( view ) + , m_commentLabel( 0 ) +{ + m_completionPopup = new QVBox( 0, 0, WType_Popup ); + m_completionPopup->setFrameStyle( QFrame::Box | QFrame::Plain ); + m_completionPopup->setLineWidth( 1 ); + + m_completionListBox = new KateCCListBox( m_completionPopup ); + m_completionListBox->setFrameStyle( QFrame::NoFrame ); + //m_completionListBox->setCornerWidget( new QSizeGrip( m_completionListBox) ); + m_completionListBox->setFocusProxy( m_view->m_viewInternal ); + + m_completionListBox->installEventFilter( this ); + + m_completionPopup->resize(m_completionListBox->sizeHint() + QSize(2,2)); + m_completionPopup->installEventFilter( this ); + m_completionPopup->setFocusProxy( m_view->m_viewInternal ); + + m_pArgHint = new KateArgHint( m_view ); + connect( m_pArgHint, SIGNAL(argHintHidden()), + this, SIGNAL(argHintHidden()) ); + + connect( m_view, SIGNAL(cursorPositionChanged()), + this, SLOT(slotCursorPosChanged()) ); +} + +KateCodeCompletion::~KateCodeCompletion() +{ + delete m_completionPopup; +} + +bool KateCodeCompletion::codeCompletionVisible () { + return m_completionPopup->isVisible(); +} + +void KateCodeCompletion::showCompletionBox( + QValueList complList, int offset, bool casesensitive ) +{ + kdDebug(13035) << "showCompletionBox " << endl; + + if ( codeCompletionVisible() ) return; + + m_caseSensitive = casesensitive; + m_complList = complList; + m_offset = offset; + m_view->cursorPositionReal( &m_lineCursor, &m_colCursor ); + m_colCursor -= offset; + + updateBox( true ); +} + +bool KateCodeCompletion::eventFilter( QObject *o, QEvent *e ) +{ + if ( o != m_completionPopup && + o != m_completionListBox && + o != m_completionListBox->viewport() ) + return false; + + if( e->type() == QEvent::Hide ) + { + //don't use abortCompletion() as aborting here again will send abort signal + //even on successfull completion we will emit completionAborted() twice... + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + return false; + } + + + if ( e->type() == QEvent::MouseButtonDblClick ) { + doComplete(); + return false; + } + + if ( e->type() == QEvent::MouseButtonPress ) { + QTimer::singleShot(0, this, SLOT(showComment())); + return false; + } + + return false; +} + +void KateCodeCompletion::handleKey (QKeyEvent *e) +{ + // close completion if you move out of range + if ((e->key() == Key_Up) && (m_completionListBox->currentItem() == 0)) + { + abortCompletion(); + m_view->setFocus(); + return; + } + + // keyboard movement + if( (e->key() == Key_Up) || (e->key() == Key_Down ) || + (e->key() == Key_Home ) || (e->key() == Key_End) || + (e->key() == Key_Prior) || (e->key() == Key_Next )) + { + QTimer::singleShot(0,this,SLOT(showComment())); + QApplication::sendEvent( m_completionListBox, (QEvent*)e ); + return; + } + + // update the box + updateBox(); +} + +void KateCodeCompletion::doComplete() +{ + KateCompletionItem* item = static_cast( + m_completionListBox->item(m_completionListBox->currentItem())); + + if( item == 0 ) + return; + + QString text = item->m_entry.text; + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); + QString add = text.mid(currentComplText.length()); + if( item->m_entry.postfix == "()" ) + add += "("; + + emit filterInsertString(&(item->m_entry),&add); + m_view->insertText(add); + + complete( item->m_entry ); + m_view->setFocus(); +} + +void KateCodeCompletion::abortCompletion() +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionAborted(); +} + +void KateCodeCompletion::complete( KTextEditor::CompletionEntry entry ) +{ + m_completionPopup->hide(); + delete m_commentLabel; + m_commentLabel = 0; + emit completionDone( entry ); + emit completionDone(); +} + +void KateCodeCompletion::updateBox( bool ) +{ + if( m_colCursor > m_view->cursorColumnReal() ) { + // the cursor is too far left + kdDebug(13035) << "Aborting Codecompletion after sendEvent" << endl; + kdDebug(13035) << m_view->cursorColumnReal() << endl; + abortCompletion(); + m_view->setFocus(); + return; + } + + m_completionListBox->clear(); + + QString currentLine = m_view->currentTextLine(); + int len = m_view->cursorColumnReal() - m_colCursor; + QString currentComplText = currentLine.mid(m_colCursor,len); +/* No-one really badly wants those, or? + kdDebug(13035) << "Column: " << m_colCursor << endl; + kdDebug(13035) << "Line: " << currentLine << endl; + kdDebug(13035) << "CurrentColumn: " << m_view->cursorColumnReal() << endl; + kdDebug(13035) << "Len: " << len << endl; + kdDebug(13035) << "Text: '" << currentComplText << "'" << endl; + kdDebug(13035) << "Count: " << m_complList.count() << endl; +*/ + QValueList::Iterator it; + if( m_caseSensitive ) { + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } else { + currentComplText = currentComplText.upper(); + for( it = m_complList.begin(); it != m_complList.end(); ++it ) { + if( (*it).text.upper().startsWith(currentComplText) ) { + new KateCompletionItem(m_completionListBox,*it); + } + } + } + + if( m_completionListBox->count() == 0 || + ( m_completionListBox->count() == 1 && // abort if we equaled the last item + currentComplText == m_completionListBox->text(0).stripWhiteSpace() ) ) { + abortCompletion(); + m_view->setFocus(); + return; + } + + kdDebug(13035)<<"KateCodeCompletion::updateBox: Resizing widget"<resize(m_completionListBox->sizeHint() + QSize(2,2)); + QPoint p = m_view->mapToGlobal( m_view->cursorCoordinates() ); + int x = p.x(); + int y = p.y() ; + if ( y + m_completionPopup->height() + m_view->renderer()->config()->fontMetrics( )->height() > QApplication::desktop()->height() ) + y -= (m_completionPopup->height() ); + else + y += m_view->renderer()->config()->fontMetrics( )->height(); + + if (x + m_completionPopup->width() > QApplication::desktop()->width()) + x = QApplication::desktop()->width() - m_completionPopup->width(); + + m_completionPopup->move( QPoint(x,y) ); + + m_completionListBox->setCurrentItem( 0 ); + m_completionListBox->setSelected( 0, true ); + m_completionListBox->setFocus(); + m_completionPopup->show(); + + QTimer::singleShot(0,this,SLOT(showComment())); +} + +void KateCodeCompletion::showArgHint ( QStringList functionList, const QString& strWrapping, const QString& strDelimiter ) +{ + unsigned int line, col; + m_view->cursorPositionReal( &line, &col ); + m_pArgHint->reset( line, col ); + m_pArgHint->setArgMarkInfos( strWrapping, strDelimiter ); + + int nNum = 0; + QStringList::Iterator end(functionList.end()); + for( QStringList::Iterator it = functionList.begin(); it != end; ++it ) + { + kdDebug(13035) << "Insert function text: " << *it << endl; + + m_pArgHint->addFunction( nNum, ( *it ) ); + + nNum++; + } + + m_pArgHint->move(m_view->mapToGlobal(m_view->cursorCoordinates() + QPoint(0,m_view->renderer()->config()->fontMetrics( )->height())) ); + m_pArgHint->show(); +} + +void KateCodeCompletion::slotCursorPosChanged() +{ + m_pArgHint->cursorPositionChanged ( m_view, m_view->cursorLine(), m_view->cursorColumnReal() ); +} + +void KateCodeCompletion::showComment() +{ + if (!m_completionPopup->isVisible()) + return; + + KateCompletionItem* item = static_cast(m_completionListBox->item(m_completionListBox->currentItem())); + + if( !item ) + return; + + if( item->m_entry.comment.isEmpty() ) + return; + + delete m_commentLabel; + m_commentLabel = new KateCodeCompletionCommentLabel( 0, item->m_entry.comment ); + m_commentLabel->setFont(QToolTip::font()); + m_commentLabel->setPalette(QToolTip::palette()); + + QPoint rightPoint = m_completionPopup->mapToGlobal(QPoint(m_completionPopup->width(),0)); + QPoint leftPoint = m_completionPopup->mapToGlobal(QPoint(0,0)); + QRect screen = QApplication::desktop()->screenGeometry ( m_commentLabel ); + QPoint finalPoint; + if (rightPoint.x()+m_commentLabel->width() > screen.x() + screen.width()) + finalPoint.setX(leftPoint.x()-m_commentLabel->width()); + else + finalPoint.setX(rightPoint.x()); + + m_completionListBox->ensureCurrentVisible(); + + finalPoint.setY( + m_completionListBox->viewport()->mapToGlobal(m_completionListBox->itemRect( + m_completionListBox->item(m_completionListBox->currentItem())).topLeft()).y()); + + m_commentLabel->move(finalPoint); + m_commentLabel->show(); +} + +KateArgHint::KateArgHint( KateView* parent, const char* name ) + : QFrame( parent, name, WType_Popup ) +{ + setBackgroundColor( black ); + setPaletteForegroundColor( Qt::black ); + + labelDict.setAutoDelete( true ); + layout = new QVBoxLayout( this, 1, 2 ); + layout->setAutoAdd( true ); + editorView = parent; + + m_markCurrentFunction = true; + + setFocusPolicy( StrongFocus ); + setFocusProxy( parent ); + + reset( -1, -1 ); +} + +KateArgHint::~KateArgHint() +{ +} + +void KateArgHint::setArgMarkInfos( const QString& wrapping, const QString& delimiter ) +{ + m_wrapping = wrapping; + m_delimiter = delimiter; + m_markCurrentFunction = true; +} + +void KateArgHint::reset( int line, int col ) +{ + m_functionMap.clear(); + m_currentFunction = -1; + labelDict.clear(); + + m_currentLine = line; + m_currentCol = col - 1; +} + +void KateArgHint::slotDone(bool completed) +{ + hide(); + + m_currentLine = m_currentCol = -1; + + emit argHintHidden(); + if (completed) + emit argHintCompleted(); + else + emit argHintAborted(); +} + +void KateArgHint::cursorPositionChanged( KateView* view, int line, int col ) +{ + if( m_currentCol == -1 || m_currentLine == -1 ){ + slotDone(false); + return; + } + + int nCountDelimiter = 0; + int count = 0; + + QString currentTextLine = view->doc()->textLine( line ); + QString text = currentTextLine.mid( m_currentCol, col - m_currentCol ); + QRegExp strconst_rx( "\"[^\"]*\"" ); + QRegExp chrconst_rx( "'[^']*'" ); + + text = text + .replace( strconst_rx, "\"\"" ) + .replace( chrconst_rx, "''" ); + + int index = 0; + while( index < (int)text.length() ){ + if( text[index] == m_wrapping[0] ){ + ++count; + } else if( text[index] == m_wrapping[1] ){ + --count; + } else if( count > 0 && text[index] == m_delimiter[0] ){ + ++nCountDelimiter; + } + ++index; + } + + if( (m_currentLine > 0 && m_currentLine != line) || (m_currentLine < col) || (count == 0) ){ + slotDone(count == 0); + return; + } + + // setCurArg ( nCountDelimiter + 1 ); + +} + +void KateArgHint::addFunction( int id, const QString& prot ) +{ + m_functionMap[ id ] = prot; + QLabel* label = new QLabel( prot.stripWhiteSpace().simplifyWhiteSpace(), this ); + label->setBackgroundColor( QColor(255, 255, 238) ); + label->show(); + labelDict.insert( id, label ); + + if( m_currentFunction < 0 ) + setCurrentFunction( id ); +} + +void KateArgHint::setCurrentFunction( int currentFunction ) +{ + if( m_currentFunction != currentFunction ){ + + if( currentFunction < 0 ) + currentFunction = (int)m_functionMap.size() - 1; + + if( currentFunction > (int)m_functionMap.size()-1 ) + currentFunction = 0; + + if( m_markCurrentFunction && m_currentFunction >= 0 ){ + QLabel* label = labelDict[ m_currentFunction ]; + label->setFont( font() ); + } + + m_currentFunction = currentFunction; + + if( m_markCurrentFunction ){ + QLabel* label = labelDict[ currentFunction ]; + QFont fnt( font() ); + fnt.setBold( true ); + label->setFont( fnt ); + } + + adjustSize(); + } +} + +void KateArgHint::show() +{ + QFrame::show(); + adjustSize(); +} + +bool KateArgHint::eventFilter( QObject*, QEvent* e ) +{ + if( isVisible() && e->type() == QEvent::KeyPress ){ + QKeyEvent* ke = static_cast( e ); + if( (ke->state() & ControlButton) && ke->key() == Key_Left ){ + setCurrentFunction( currentFunction() - 1 ); + ke->accept(); + return true; + } else if( ke->key() == Key_Escape ){ + slotDone(false); + return false; + } else if( (ke->state() & ControlButton) && ke->key() == Key_Right ){ + setCurrentFunction( currentFunction() + 1 ); + ke->accept(); + return true; + } + } + + return false; +} + +void KateArgHint::adjustSize( ) +{ + QRect screen = QApplication::desktop()->screenGeometry( pos() ); + + QFrame::adjustSize(); + if( width() > screen.width() ) + resize( screen.width(), height() ); + + if( x() + width() > screen.x() + screen.width() ) + move( screen.x() + screen.width() - width(), y() ); +} + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodecompletion.h b/kate/part/katecodecompletion.h new file mode 100644 index 000000000..81279d929 --- /dev/null +++ b/kate/part/katecodecompletion.h @@ -0,0 +1,164 @@ +/* This file is part of the KDE libraries + + Copyright (C) 2001 Joseph Wenninger + Copyright (C) 2002 John Firebaugh + Copyright (C) 2001 by Victor Röder + Copyright (C) 2002 by Roberto Raggi + + 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 02110-1301, USA. +*/ + +/******** Partly based on the ArgHintWidget of Qt3 by Trolltech AS *********/ +/* Trolltech doesn't mind, if we license that piece of code as LGPL, because there isn't much + * left from the desigener code */ + + +#ifndef __KateCodeCompletion_H__ +#define __KateCodeCompletion_H__ + +#include + +#include +#include +#include +#include +#include +#include + +class KateView; +class KateArgHint; +class KateCCListBox; + +class QLayout; +class QVBox; + +class KateCodeCompletionCommentLabel : public QLabel +{ + Q_OBJECT + + public: + KateCodeCompletionCommentLabel( QWidget* parent, const QString& text) : QLabel( parent, "toolTipTip", + WStyle_StaysOnTop | WStyle_Customize | WStyle_NoBorder | WStyle_Tool | WX11BypassWM ) + { + setMargin(1); + setIndent(0); + setAutoMask( false ); + setFrameStyle( QFrame::Plain | QFrame::Box ); + setLineWidth( 1 ); + setAlignment( AlignAuto | AlignTop ); + polish(); + setText(text); + adjustSize(); + } +}; + +class KateCodeCompletion : public QObject +{ + Q_OBJECT + + friend class KateViewInternal; + + public: + KateCodeCompletion(KateView *view); + ~KateCodeCompletion(); + + bool codeCompletionVisible (); + + void showArgHint( + QStringList functionList, const QString& strWrapping, const QString& strDelimiter ); + void showCompletionBox( + QValueList entries, int offset = 0, bool casesensitive = true ); + bool eventFilter( QObject* o, QEvent* e ); + + void handleKey (QKeyEvent *e); + + public slots: + void slotCursorPosChanged(); + void showComment(); + void updateBox () { updateBox(false); } + + signals: + void completionAborted(); + void completionDone(); + void argHintHidden(); + void completionDone(KTextEditor::CompletionEntry); + void filterInsertString(KTextEditor::CompletionEntry*,QString *); + + private: + void doComplete(); + void abortCompletion(); + void complete( KTextEditor::CompletionEntry ); + void updateBox( bool newCoordinate ); + + KateArgHint* m_pArgHint; + KateView* m_view; + QVBox* m_completionPopup; + KateCCListBox* m_completionListBox; + QValueList m_complList; + uint m_lineCursor; + uint m_colCursor; + int m_offset; + bool m_caseSensitive; + KateCodeCompletionCommentLabel* m_commentLabel; +}; + +class KateArgHint: public QFrame +{ + Q_OBJECT + + public: + KateArgHint( KateView* =0, const char* =0 ); + virtual ~KateArgHint(); + + virtual void setCurrentFunction( int ); + virtual int currentFunction() const { return m_currentFunction; } + + void setArgMarkInfos( const QString&, const QString& ); + + virtual void addFunction( int, const QString& ); + QString functionAt( int id ) const { return m_functionMap[ id ]; } + + virtual void show(); + virtual void adjustSize(); + virtual bool eventFilter( QObject*, QEvent* ); + + signals: + void argHintHidden(); + void argHintCompleted(); + void argHintAborted(); + + public slots: + virtual void reset( int, int ); + virtual void cursorPositionChanged( KateView*, int, int ); + + private slots: + void slotDone(bool completed); + + private: + QMap m_functionMap; + int m_currentFunction; + QString m_wrapping; + QString m_delimiter; + bool m_markCurrentFunction; + int m_currentLine; + int m_currentCol; + KateView* editorView; + QIntDict labelDict; + QLayout* layout; +}; + +#endif + +// kate: space-indent on; indent-width 2; replace-tabs on; diff --git a/kate/part/katecodefoldinghelpers.cpp b/kate/part/katecodefoldinghelpers.cpp new file mode 100644 index 000000000..49090820b --- /dev/null +++ b/kate/part/katecodefoldinghelpers.cpp @@ -0,0 +1,1662 @@ +/* This file is part of the KDE libraries + Copyright (C) 2002 Joseph Wenninger + + 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 02110-1301, USA. +*/ + +#include "katecodefoldinghelpers.h" +#include "katecodefoldinghelpers.moc" + +#include "katebuffer.h" +#include "katecursor.h" +#include + +#include + +#define JW_DEBUG 0 + +bool KateCodeFoldingTree::trueVal = true; + +KateCodeFoldingNode::KateCodeFoldingNode() : + parentNode(0), + startLineRel(0), + endLineRel(0), + startCol(0), + endCol(0), + startLineValid(false), + endLineValid(false), + type(0), + visible(true), + deleteOpening(false), + deleteEnding(false) +{ +}//the endline fields should be initialised to not valid + +KateCodeFoldingNode::KateCodeFoldingNode(KateCodeFoldingNode *par, signed char typ, unsigned int sLRel): + parentNode(par), + startLineRel(sLRel), + endLineRel(10000), + startCol(0), + endCol(0), + startLineValid(true), + endLineValid(false), + type(typ), + visible(true), + deleteOpening(false), + deleteEnding(false) +{ +}//the endline fields should be initialised to not valid + +KateCodeFoldingNode::~KateCodeFoldingNode() +{ + // delete all child nodes + clearChildren (); +} + +bool KateCodeFoldingNode::getBegin(KateCodeFoldingTree *tree, KateTextCursor* begin) { + if (!startLineValid) return false; + unsigned int line=startLineRel; + for (KateCodeFoldingNode *n=parentNode;n;n=n->parentNode) + line+=n->startLineRel; + + tree->m_buffer->codeFoldingColumnUpdate(line); + begin->setLine(line); + begin->setCol(startCol); + + return true; +} + +bool KateCodeFoldingNode::getEnd(KateCodeFoldingTree *tree, KateTextCursor *end) { + if (!endLineValid) return false; + unsigned int line=startLineRel+endLineRel; + for (KateCodeFoldingNode *n=parentNode;n;n=n->parentNode) + line+=n->startLineRel; + + tree->m_buffer->codeFoldingColumnUpdate(line); + end->setLine(line); + end->setCol(endCol); + + return true; +} + +int KateCodeFoldingNode::cmpPos(KateCodeFoldingTree *tree, uint line,uint col) { + KateTextCursor cur(line,col); + KateTextCursor start,end; + kdDebug(13000)<<"KateCodeFoldingNode::cmpPos (1)"<cur)?-1:0); + } + if ((!startValid) && endValid) { + return ((cur>end)?1:0); + } + //here both have to be valid, both invalid must not happen + Q_ASSERT(startValid && endValid); + return ( (curend) ? 1:0)); +} + +void KateCodeFoldingNode::insertChild (uint index, KateCodeFoldingNode *node) +{ + uint s = m_children.size (); + + if (index > s) + return; + + m_children.resize (++s); + + for (uint i=s-1; i > index; --i) + m_children[i] = m_children[i-1]; + + m_children[index] = node; +} + +KateCodeFoldingNode *KateCodeFoldingNode::takeChild (uint index) +{ + uint s = m_children.size (); + + if (index >= s) + return 0; + + KateCodeFoldingNode *n = m_children[index]; + + for (uint i=index; (i+1) < s; ++i) + m_children[i] = m_children[i+1]; + + m_children.resize (s-1); + + return n; +} + +void KateCodeFoldingNode::clearChildren () +{ + for (uint i=0; i < m_children.size(); ++i) + delete m_children[i]; + + m_children.resize (0); +} + +KateCodeFoldingTree::KateCodeFoldingTree(KateBuffer *buffer): QObject(buffer), m_buffer (buffer) +{ + clear(); +} + +void KateCodeFoldingTree::fixRoot(int endLRel) +{ + m_root.endLineRel = endLRel; +} + +void KateCodeFoldingTree::clear() +{ + m_root.clearChildren(); + + // initialize the root "special" node + m_root.startLineValid=true; + m_root.endLineValid=true; // temporary, should be false; + m_root.endLineRel=1; // temporary; + + hiddenLinesCountCacheValid=false; + lineMapping.setAutoDelete(true); + hiddenLines.clear(); + lineMapping.clear(); + nodesForLine.clear(); + markedForDeleting.clear(); + dontIgnoreUnchangedLines.clear(); +} + +KateCodeFoldingTree::~KateCodeFoldingTree() +{ +} + +bool KateCodeFoldingTree::isTopLevel(unsigned int line) +{ + if (m_root.noChildren()) + return true; // no childs + + // look if a given lines belongs to a sub node + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) + return false; // the line is within the range of a subnode -> return toplevel=false + } + + return true; // the root node is the only node containing the given line, return toplevel=true +} + +void KateCodeFoldingTree::getLineInfo(KateLineInfo *info, unsigned int line) +{ + // Initialze the returned structure, this will also be returned if the root node has no child nodes + // or the line is not within a childnode's range. + info->topLevel = true; + info->startsVisibleBlock = false; + info->startsInVisibleBlock = false; + info->endsBlock = false; + info->invalidBlockEnd = false; + + if (m_root.noChildren()) + return; + + //let's look for some information + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) // we found a node, which contains the given line -> do a complete lookup + { + info->topLevel = false; //we are definitly not toplevel + findAllNodesOpenedOrClosedAt(line); //lookup all nodes, which start or and at the given line + + for ( KateCodeFoldingNode *node = nodesForLine.first(); node; node = nodesForLine.next() ) + { + uint startLine = getStartLine(node); + + // type<0 means, that a region has been closed, but not opened + // eg. parantheses missmatch + if (node->type < 0) + info->invalidBlockEnd=true; + else + { + if (startLine != line) // does the region we look at not start at the given line + info->endsBlock = true; // than it has to be an ending + else + { + // The line starts a new region, now determine, if it's a visible or a hidden region + if (node->visible) + info->startsVisibleBlock=true; + else + info->startsInVisibleBlock=true; + } + } + } + + return; + } + } + + return; +} + + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForLine(unsigned int line) +{ + if (m_root.noChildren()) // does we have child list + nodes ? + return &m_root; + + // lets look, if given line is within a subnode range, and then return the deepest one. + for ( uint i=0; i < m_root.childCount(); ++i ) + { + KateCodeFoldingNode *node = m_root.child(i); + + if ((node->startLineRel<=line) && (line<=node->startLineRel+node->endLineRel)) + { + // a region surounds the line, look in the next deeper hierarchy step + return findNodeForLineDescending(node,line,0); + } + } + + return &m_root; +} + + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForLineDescending ( KateCodeFoldingNode *node, + unsigned int line, unsigned int offset, bool oneStepOnly ) +{ + if (node->noChildren()) + return node; + + // calculate the offset, between a subnodes real start line and its relative start + offset += node->startLineRel; + + for ( uint i=0; i < node->childCount(); ++i ) + { + KateCodeFoldingNode *subNode = node->child(i); + + if ((subNode->startLineRel+offset<=line) && (line<=subNode->endLineRel+subNode->startLineRel+offset)) //warning fix me for invalid ends + { + // a subnode contains the line. + // if oneStepOnly is true, we don't want to search for the deepest node, just return the found one + + if (oneStepOnly) + return subNode; + else + return findNodeForLineDescending (subNode,line,offset); // look into the next deeper hierarchy step + } + } + + return node; // the current node has no sub nodes, or the line couldn'te be found within a subregion +} + +KateCodeFoldingNode *KateCodeFoldingTree::findNodeForPosition(unsigned int line, unsigned int column) +{ + KateCodeFoldingNode *node=findNodeForLine(line); + + if (node==&m_root) return &m_root; + + kdDebug(13000)<<"initial cmpPos"<cmpPos(this, line,column); + while (true) { + switch (leq) { + case 0: { + if (node->noChildren()) + return node; + else + { + tmp=node; + for ( uint i=0; i < node->childCount(); ++i ) + { + KateCodeFoldingNode *subNode = node->child(i); + kdDebug(13000)<<"cmdPos(case0):calling"<cmpPos(this, line,column); + kdDebug(13000)<<"cmdPos(case0):returned"<parentNode)) return &m_root; + kdDebug(13000)<<"current node type"<type<parentNode; + kdDebug(13000)<<"cmdPos(case-1/1):calling:"<cmpPos(this, line,column); + kdDebug(13000)<<"cmdPos(case-1/1):returned"<type).arg(node->startLineValid).arg(node->startLineRel).arg(node->endLineValid). + arg(node->endLineRel).arg(node->visible)<noChildren()) + return; + + QString newprefix(prefix + " "); + for ( uint i=0; i < node->childCount(); ++i ) + dumpNode (node->child(i),newprefix); +} + +/* + That's one of the most important functions ;) +*/ +void KateCodeFoldingTree::updateLine(unsigned int line, + QMemArray *regionChanges, bool *updated,bool changed,bool colsChanged) +{ + if ( (!changed) || colsChanged) + { + if (dontIgnoreUnchangedLines.isEmpty()) + return; + + if (dontIgnoreUnchangedLines[line]) + dontIgnoreUnchangedLines.remove(line); + else + return; + } + + something_changed = false; + + findAndMarkAllNodesforRemovalOpenedOrClosedAt(line); + + if (regionChanges->isEmpty()) + { + // KateCodeFoldingNode *node=findNodeForLine(line); + // if (node->type!=0) + // if (getStartLine(node)+node->endLineRel==line) removeEnding(node,line); + } + else + { + for (unsigned int i=0;isize() / 4;i++) + { + signed char tmp=(*regionChanges)[regionChanges->size()-2-i*2]; + uint tmppos=(*regionChanges)[regionChanges->size()-1-i*2]; + (*regionChanges)[regionChanges->size()-2-i*2]=(*regionChanges)[i*2]; + (*regionChanges)[regionChanges->size()-1-i*2]=(*regionChanges)[i*2+1]; + (*regionChanges)[i*2]=tmp; + (*regionChanges)[i*2+1]=tmppos; + } + + + signed char data= (*regionChanges)[regionChanges->size()-2]; + uint charPos=(*regionChanges)[regionChanges->size()-1]; + regionChanges->resize (regionChanges->size()-2); + + int insertPos=-1; + KateCodeFoldingNode *node = findNodeForLine(line); + + if (data<0) + { + // if (insertPos==-1) + { + unsigned int tmpLine=line-getStartLine(node); + + for ( uint i=0; i < node->childCount(); ++i ) + { + if (node->child(i)->startLineRel >= tmpLine) + { + insertPos=i; + break; + } + } + } + } + else + { + for (; (node->parentNode) && (getStartLine(node->parentNode)==line) && (node->parentNode->type!=0); node=node->parentNode); + + if ((getStartLine(node)==line) && (node->type!=0)) + { + insertPos=node->parentNode->findChild(node); + node = node->parentNode; + } + else + { + for ( uint i=0; i < node->childCount(); ++i ) + { + if (getStartLine(node->child(i))>=line) + { + insertPos=i; + break; + } + } + } + } + + do + { + if (data<0) + { + if (correctEndings(data,node,line,charPos,insertPos)) + { + insertPos=node->parentNode->findChild(node)+1; + node=node->parentNode; + } + else + { + if (insertPos!=-1) insertPos++; + } + } + else + { + int startLine=getStartLine(node); + if ((insertPos==-1) || (insertPos>=(int)node->childCount())) + { + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,data,line-startLine); + something_changed = true; + node->appendChild(newNode); + addOpening(newNode, data, regionChanges, line,charPos); + insertPos = node->findChild(newNode)+1; + } + else + { + if (node->child(insertPos)->startLineRel == line-startLine) + { + addOpening(node->child(insertPos), data, regionChanges, line,charPos); + insertPos++; + } + else + { +// kdDebug(13000)<<"ADDING NODE "<insertChild(insertPos, newNode); + addOpening(newNode, data, regionChanges, line,charPos); + insertPos++; + } + } + } + + if (regionChanges->isEmpty()) + data = 0; + else + { + data = (*regionChanges)[regionChanges->size()-2]; + charPos=(*regionChanges)[regionChanges->size()-1]; + regionChanges->resize (regionChanges->size()-2); + } + } while (data!=0); + } + + cleanupUnneededNodes(line); +// if (something_changed) emit regionBeginEndAddedRemoved(line); + (*updated) = something_changed; +} + + +bool KateCodeFoldingTree::removeOpening(KateCodeFoldingNode *node,unsigned int line) +{ + signed char type; + if ((type=node->type) == 0) + { + dontDeleteOpening(node); + dontDeleteEnding(node); + return false; + } + + if (!node->visible) + { + toggleRegionVisibility(getStartLine(node)); + } + + KateCodeFoldingNode *parent = node->parentNode; + int mypos = parent->findChild(node); + + if (mypos > -1) + { + //move childnodes() up + for(; node->childCount()>0 ;) + { + KateCodeFoldingNode *tmp; + parent->insertChild(mypos, tmp=node->takeChild(0)); + tmp->parentNode = parent; + tmp->startLineRel += node->startLineRel; + mypos++; + } + + // remove the node + //mypos = parent->findChild(node); + bool endLineValid = node->endLineValid; + int endLineRel = node->endLineRel; + uint endCol=node->endCol; + + // removes + deletes + KateCodeFoldingNode *child = parent->takeChild(mypos); + markedForDeleting.removeRef(child); + delete child; + + if ((type>0) && (endLineValid)) + correctEndings(-type, parent, line+endLineRel/*+1*/,endCol, mypos); // why the hell did I add a +1 here ? + } + + return true; +} + +bool KateCodeFoldingTree::removeEnding(KateCodeFoldingNode *node,unsigned int /* line */) +{ + KateCodeFoldingNode *parent = node->parentNode; + + if (!parent) + return false; + + if (node->type == 0) + return false; + + if (node->type < 0) + { + // removes + deletes + int i = parent->findChild (node); + if (i >= 0) + { + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef(child); + delete child; + } + + return true; + } + + int mypos = parent->findChild(node); + int count = parent->childCount(); + + for (int i=mypos+1; ichild(i)->type == -node->type) + { + node->endLineValid = true; + node->endLineRel = parent->child(i)->startLineRel - node->startLineRel; + + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef(child); + delete child; + + count = i-mypos-1; + if (count > 0) + { + for (int i=0; itakeChild(mypos+1); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; //should help 16.04.2002 + node->appendChild(tmp); + } + } + return false; + } + } + + if ( (parent->type == node->type) || /*temporary fix */ (!parent->parentNode)) + { + for (int i=mypos+1; i<(int)parent->childCount(); i++) + { + KateCodeFoldingNode *tmp = parent->takeChild(mypos+1); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; // SHOULD HELP 16.04.2002 + node->appendChild(tmp); + } + + // this should fix the bug of wrongly closed nodes + if (!parent->parentNode) + node->endLineValid=false; + else + node->endLineValid = parent->endLineValid; + + node->endLineRel = parent->endLineRel-node->startLineRel; + + if (node->endLineValid) + return removeEnding(parent, getStartLine(parent)+parent->endLineRel); + + return false; + } + + node->endLineValid = false; + node->endLineRel = parent->endLineRel - node->startLineRel; + + return false; +} + + +bool KateCodeFoldingTree::correctEndings(signed char data, KateCodeFoldingNode *node,unsigned int line,unsigned int endCol,int insertPos) +{ +// if (node->type==0) {kdError()<<"correct Ending should never be called with the root node"<type) + { +#if JW_DEBUG + kdDebug(13000)<<"data!=-node->type (correctEndings)"< add to unopend list + dontDeleteEnding(node); + if (data == node->type) { + node->endCol=endCol; + return false; + } + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,data,line-startLine); + something_changed = true; + newNode->startLineValid = false; + newNode->endLineValid = true; + newNode->endLineRel = 0; + newNode->endCol=endCol; + + if ((insertPos==-1) || (insertPos==(int)node->childCount())) + node->appendChild(newNode); + else + node->insertChild(insertPos,newNode); + + // find correct position + return false; + } + else + { + something_changed = true; + dontDeleteEnding(node); + + // valid closing region + if (!node->endLineValid) + { + node->endLineValid = true; + node->endLineRel = line - startLine; + node->endCol=endCol; + //moving + + moveSubNodesUp(node); + } + else + { +#if JW_DEBUG + kdDebug(13000)<<"Closing a node which had already a valid end"<endLineRel == line) + { + node->endCol=endCol; + // we won, just skip +#if JW_DEBUG + kdDebug(13000)<< "We won, just skipping (correctEndings)"<endLineRel+startLine; + uint bakEndCol = node->endCol; + node->endLineRel = line-startLine; + node->endCol=endCol; + +#if JW_DEBUG + kdDebug(13000)<< "reclosed node had childnodes()"<parentNode) + { + correctEndings(data,node->parentNode,bakEndLine, bakEndCol,node->parentNode->findChild(node)+1); // ???? + } + else + { + //add to unopened list (bakEndLine) + } + } + } + } + return true; +} + +void KateCodeFoldingTree::moveSubNodesUp(KateCodeFoldingNode *node) +{ + int mypos = node->parentNode->findChild(node); + int removepos=-1; + int count = node->childCount(); + for (int i=0; ichild(i)->startLineRel >= node->endLineRel) + { + removepos=i; + break; + } +#if JW_DEBUG + kdDebug(13000)<-1) + { +#if JW_DEBUG + kdDebug(13000)<<"Children need to be moved"<parentNode->childCount()-1) + { + while (removepos<(int)node->childCount()) + { + node->parentNode->appendChild(moveNode=node->takeChild(removepos)); + moveNode->parentNode = node->parentNode; + moveNode->startLineRel += node->startLineRel; + } + } + else + { + int insertPos=mypos; + while (removepos < (int)node->childCount()) + { + insertPos++; + node->parentNode->insertChild(insertPos, moveNode=node->takeChild(removepos)); + moveNode->parentNode = node->parentNode; // That should solve a crash + moveNode->startLineRel += node->startLineRel; + } + } + } + +} + + + +void KateCodeFoldingTree::addOpening(KateCodeFoldingNode *node,signed char nType, QMemArray* list,unsigned int line,unsigned int charPos) +{ + uint startLine = getStartLine(node); + if ((startLine==line) && (node->type!=0)) + { +#if JW_DEBUG + kdDebug(13000)<<"startLine equals line"<type) + { +#if JW_DEBUG + kdDebug(13000)<<"Node exists"<deleteOpening = false; + node->startCol=charPos; + KateCodeFoldingNode *parent = node->parentNode; + + if (!node->endLineValid) + { + int current = parent->findChild(node); + int count = parent->childCount()-(current+1); + node->endLineRel = parent->endLineRel - node->startLineRel; + +// EXPERIMENTAL TEST BEGIN +// move this afte the test for unopened, but closed regions within the parent node, or if there are no siblings, bubble up + if (parent) + if (parent->type == node->type) + { + if (parent->endLineValid) + { + removeEnding(parent, line); + node->endLineValid = true; + } + } + +// EXPERIMENTAL TEST BEGIN + + if (current != (int)parent->childCount()-1) + { + //search for an unopened but closed region, even if the parent is of the same type +#ifdef __GNUC__ +#warning "FIXME: why does this seem to work?" +#endif +// if (node->type != parent->type) + { + for (int i=current+1; i<(int)parent->childCount(); i++) + { + if (parent->child(i)->type == -node->type) + { + count = (i-current-1); + node->endLineValid = true; + node->endLineRel = getStartLine(parent->child(i))-line; + node->endCol = parent->child(i)->endCol; + KateCodeFoldingNode *child = parent->takeChild(i); + markedForDeleting.removeRef( child ); + delete child; + break; + } + } + } +// else +// { +// parent->endLineValid = false; +// parent->endLineRel = 20000; +// } + + if (count>0) + { + for (int i=0;iappendChild(tmp=parent->takeChild(current+1)); + tmp->startLineRel -= node->startLineRel; + tmp->parentNode = node; + } + } + } + + } + + addOpening_further_iterations(node, nType, list, line, 0, startLine,node->startCol); + + } //else ohoh, much work to do same line, but other region type + } + else + { // create a new region + KateCodeFoldingNode *newNode = new KateCodeFoldingNode (node,nType,line-startLine); + something_changed = true; + + int insert_position=-1; + for (int i=0; i<(int)node->childCount(); i++) + { + if (startLine+node->child(i)->startLineRel > line) + { + insert_position=i; + break; + } + } + + int current; + if (insert_position==-1) + { + node->appendChild(newNode); + current = node->childCount()-1; + } + else + { + node->insertChild(insert_position, newNode); + current = insert_position; + } + +// if (node->type==newNode->type) +// { +// newNode->endLineValid=true; +// node->endLineValid=false; +// newNode->endLineRel=node->endLineRel-newNode->startLineRel; +// node->endLineRel=20000; //FIXME + + int count = node->childCount() - (current+1); + newNode->endLineRel -= newNode->startLineRel; + if (current != (int)node->childCount()-1) + { + if (node->type != newNode->type) + { + for (int i=current+1; i<(int)node->childCount(); i++) + { + if (node->child(i)->type == -newNode->type) + { + count = node->childCount() - i - 1; + newNode->endLineValid = true; + newNode->endLineRel = line - getStartLine(node->child(i)); + KateCodeFoldingNode *child = node->takeChild(i); + markedForDeleting.removeRef( child ); + delete child; + break; + } + } + } + else + { + node->endLineValid = false; + node->endLineRel = 10000; + } + if (count > 0) + { + for (int i=0;iappendChild(tmp=node->takeChild(current+1)); + tmp->parentNode=newNode; + } + } +// } + } + + addOpening(newNode, nType, list, line,charPos); + + addOpening_further_iterations(node, node->type, list, line, current, startLine,node->startCol); + } +} + + +void KateCodeFoldingTree::addOpening_further_iterations(KateCodeFoldingNode *node,signed char /* nType */, QMemArray* + list,unsigned int line,int current, unsigned int startLine,unsigned int charPos) +{ + while (!(list->isEmpty())) + { + if (list->isEmpty()) + return; + else + { + signed char data = (*list)[list->size()-2]; + uint charPos=(*list)[list->size()-1]; + list->resize (list->size()-2); + + if (data<0) + { +#if JW_DEBUG + kdDebug(13000)<<"An ending was found"<endLineValid) + { + if (node->endLineRel+startLine==line) // We've won again + { + //handle next node; + } + else + { // much moving + node->endLineRel=line-startLine; + node->endLineValid=true; + } + return; // next higher level should do the rest + } + else + { + node->endLineRel=line-startLine; + node->endLineValid=true; + //much moving + } + } //else add to unopened list +#endif + } + else + { + bool needNew = true; + if (current < (int)node->childCount()) + { + if (getStartLine(node->child(current)) == line) + needNew=false; + } + if (needNew) + { + something_changed = true; + KateCodeFoldingNode *newNode = new KateCodeFoldingNode(node, data, line-startLine); + node->insertChild(current, newNode); //find the correct position later + } + + addOpening(node->child(current), data, list, line,charPos); + current++; + //lookup node or create subnode + } + } + } // end while +} + +unsigned int KateCodeFoldingTree::getStartLine(KateCodeFoldingNode *node) +{ + unsigned int lineStart=0; + for (KateCodeFoldingNode *iter=node; iter->type != 0; iter=iter->parentNode) + lineStart += iter->startLineRel; + + return lineStart; +} + + +void KateCodeFoldingTree::lineHasBeenRemoved(unsigned int line) +{ + lineMapping.clear(); + dontIgnoreUnchangedLines.insert(line, &trueVal); + dontIgnoreUnchangedLines.insert(line-1, &trueVal); + dontIgnoreUnchangedLines.insert(line+1, &trueVal); + hiddenLinesCountCacheValid = false; +#if JW_DEBUG + kdDebug(13000)<endLineValid) + { + int startLine = getStartLine(node); + if (startLine == (int)line) + node->startLineRel--; + else + { + if (node->endLineRel == 0) + node->endLineValid = false; + node->endLineRel--; + } + + int count = node->childCount(); + for (int i=0; ichild(i)->startLineRel+startLine >= line) + node->child(i)->startLineRel--; + } + } + + if (node->parentNode) + decrementBy1(node->parentNode, node); + + for (QValueList::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if ((*it).start > line) + (*it).start--; + else if ((*it).start+(*it).length > line) + (*it).length--; + } +} + + +void KateCodeFoldingTree::decrementBy1(KateCodeFoldingNode *node, KateCodeFoldingNode *after) +{ + if (node->endLineRel == 0) + node->endLineValid = false; + node->endLineRel--; + + for (uint i=node->findChild(after)+1; i < node->childCount(); ++i) + node->child(i)->startLineRel--; + + if (node->parentNode) + decrementBy1(node->parentNode,node); +} + + +void KateCodeFoldingTree::lineHasBeenInserted(unsigned int line) +{ + lineMapping.clear(); + dontIgnoreUnchangedLines.insert(line, &trueVal); + dontIgnoreUnchangedLines.insert(line-1, &trueVal); + dontIgnoreUnchangedLines.insert(line+1, &trueVal); + hiddenLinesCountCacheValid = false; +//return; +#if JW_DEBUG + kdDebug(13000)<endLineValid) + { + int startLine=getStartLine(node); + if (node->type < 0) + node->startLineRel++; + else + node->endLineRel++; + + for (uint i=0; i < node->childCount(); ++i) + { + KateCodeFoldingNode *iter = node->child(i); + + if (iter->startLineRel+startLine >= line) + iter->startLineRel++; + } + } + + if (node->parentNode) + incrementBy1(node->parentNode, node); + + for (QValueList::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if ((*it).start > line) + (*it).start++; + else if ((*it).start+(*it).length > line) + (*it).length++; + } +} + +void KateCodeFoldingTree::incrementBy1(KateCodeFoldingNode *node, KateCodeFoldingNode *after) +{ + node->endLineRel++; + + for (uint i=node->findChild(after)+1; i < node->childCount(); ++i) + node->child(i)->startLineRel++; + + if (node->parentNode) + incrementBy1(node->parentNode,node); +} + + +void KateCodeFoldingTree::findAndMarkAllNodesforRemovalOpenedOrClosedAt(unsigned int line) +{ +#ifdef __GNUC__ +#warning "FIXME: make this multiple region changes per line save"; +#endif +// return; + markedForDeleting.clear(); + KateCodeFoldingNode *node = findNodeForLine(line); + if (node->type == 0) + return; + + addNodeToRemoveList(node, line); + + while (((node->parentNode) && (node->parentNode->type!=0)) && (getStartLine(node->parentNode)==line)) + { + node = node->parentNode; + addNodeToRemoveList(node, line); + } +#if JW_DEBUG + kdDebug(13000)<<" added line to markedForDeleting list"<startLineValid)) + { + add=true; + node->deleteOpening = true; + } + if ((startLine+node->endLineRel==line) || ((node->endLineValid==false) && (node->deleteOpening))) + { + int myPos=node->parentNode->findChild(node); // this has to be implemented nicely + if ((int)node->parentNode->childCount()>myPos+1) + addNodeToRemoveList(node->parentNode->child(myPos+1),line); + add=true; + node->deleteEnding = true; + } + + if(add) + markedForDeleting.append(node); + +} + + +void KateCodeFoldingTree::findAllNodesOpenedOrClosedAt(unsigned int line) +{ + nodesForLine.clear(); + KateCodeFoldingNode *node = findNodeForLine(line); + if (node->type == 0) + return; + + unsigned int startLine = getStartLine(node); + if (startLine == line) + nodesForLine.append(node); + else if ((startLine+node->endLineRel == line)) + nodesForLine.append(node); + + while (node->parentNode) + { + addNodeToFoundList(node->parentNode, line, node->parentNode->findChild(node)); + node = node->parentNode; + } +#if JW_DEBUG + kdDebug(13000)<<" added line to nodesForLine list"<type!=0)) + nodesForLine.append(node); + else if ((startLine+node->endLineRel==line) && (node->type!=0)) + nodesForLine.append(node); + + for (int i=childpos+1; i<(int)node->childCount(); i++) + { + KateCodeFoldingNode *child = node->child(i); + + if (startLine+child->startLineRel == line) + { + nodesForLine.append(child); + addNodeToFoundList(child, line, 0); + } + else + break; + } +} + + +void KateCodeFoldingTree::cleanupUnneededNodes(unsigned int line) +{ +#if JW_DEBUG + kdDebug(13000)<<"void KateCodeFoldingTree::cleanupUnneededNodes(unsigned int line)"<deleteOpening) + kdDebug(13000)<<"DELETE OPENING SET"<deleteEnding) + kdDebug(13000)<<"DELETE ENDING SET"<deleteOpening) && (node->deleteEnding)) + { +#if JW_DEBUG + kdDebug(13000)<<"Deleting complete node"<endLineValid) // just delete it, it has been opened and closed on this line + { + int f = node->parentNode->findChild (node); + + if (f >= 0) + delete node->parentNode->takeChild(f); + } + else + { + removeOpening(node, line); + // the node has subnodes which need to be moved up and this one has to be deleted + } + something_changed = true; + } + else + { + if ((node->deleteOpening) && (node->startLineValid)) + { +#if JW_DEBUG + kdDebug(13000)<<"calling removeOpening"<deleteEnding) && (node->endLineValid)) + { + dontDeleteEnding(node); + removeEnding(node, line); + something_changed = true; + } + else + dontDeleteEnding(node); + } + } + } +} + +void KateCodeFoldingTree::dontDeleteEnding(KateCodeFoldingNode* node) +{ + node->deleteEnding = false; +} + + +void KateCodeFoldingTree::dontDeleteOpening(KateCodeFoldingNode* node) +{ + node->deleteOpening = false; +} + + +void KateCodeFoldingTree::toggleRegionVisibility(unsigned int line) +{ + // hl whole file + m_buffer->line (m_buffer->count()-1); + + lineMapping.clear(); + hiddenLinesCountCacheValid = false; + kdDebug(13000)<startLineValid) || (getStartLine(node) != line) ) + { + nodesForLine.remove(i); + i--; + } + } + + if (nodesForLine.isEmpty()) + return; + + nodesForLine.at(0)->visible = !nodesForLine.at(0)->visible; + + if (!nodesForLine.at(0)->visible) + addHiddenLineBlock(nodesForLine.at(0),line); + else + { + for (QValueList::Iterator it=hiddenLines.begin(); it!=hiddenLines.end();++it) + if ((*it).start == line+1) + { + hiddenLines.remove(it); + break; + } + + updateHiddenSubNodes(nodesForLine.at(0)); + } + + emit regionVisibilityChangedAt(line); +} + +void KateCodeFoldingTree::updateHiddenSubNodes(KateCodeFoldingNode *node) +{ + for (uint i=0; i < node->childCount(); ++i) + { + KateCodeFoldingNode *iter = node->child(i); + + if (!iter->visible) + addHiddenLineBlock(iter, getStartLine(iter)); + else + updateHiddenSubNodes(iter); + } +} + +void KateCodeFoldingTree::addHiddenLineBlock(KateCodeFoldingNode *node,unsigned int line) +{ + KateHiddenLineBlock data; + data.start = line+1; + data.length = node->endLineRel-(existsOpeningAtLineAfter(line+node->endLineRel,node)?1:0); // without -1; + bool inserted = false; + + for (QValueList::Iterator it=hiddenLines.begin(); it!=hiddenLines.end(); ++it) + { + if (((*it).start>=data.start) && ((*it).start<=data.start+data.length-1)) // another hidden block starting at the within this block already exits -> adapt new block + { + // the existing block can't have lines behind the new one, because a newly hidden + // block has to encapsulate already hidden ones + it=hiddenLines.remove(it); + --it; + } + else + { + if ((*it).start > line) + { + hiddenLines.insert(it, data); + inserted = true; + + break; + } + } + } + + if (!inserted) + hiddenLines.append(data); +} + +bool KateCodeFoldingTree::existsOpeningAtLineAfter(unsigned int line, KateCodeFoldingNode *node) +{ + for(KateCodeFoldingNode *tmp = node->parentNode; tmp; tmp=tmp->parentNode) + { + KateCodeFoldingNode *tmp2; + unsigned int startLine=getStartLine(tmp); + + if ((tmp2 = tmp->child(tmp->findChild(node) + 1)) + && ((tmp2->startLineRel + startLine) == line)) + return true; + + if ((startLine + tmp->endLineRel) > line) + return false; + } + + return false; +} + + +// +// get the real line number for a virtual line +// +unsigned int KateCodeFoldingTree::getRealLine(unsigned int virtualLine) +{ + // he, if nothing is hidden, why look at it ;) + if (hiddenLines.isEmpty()) + return virtualLine; + + // kdDebug(13000)<::ConstIterator it=hiddenLines.begin();it!=hiddenLines.end();++it) + { + if ((*it).start<=virtualLine) + virtualLine += (*it).length; + else + break; + } + + // kdDebug(13000)< %1").arg(realLine)<::ConstIterator it=hiddenLines.fromLast(); it!=hiddenLines.end(); --it) + { + if ((*it).start <= realLine) + realLine -= (*it).length; + // else + // break; + } + + // kdDebug(13000)<virtual Line %1").arg(realLine)<