/* This file is part of the KDE libraries and the Kate part. * * Copyright (C) 2005 Hamish Rodda * Copyright (C) 2008 Dominik Haumann * * This library is free software; you can redistribute it and/or * modify it under the terms of the GNU Library General Public * License as published by the Free Software Foundation; either * version 2 of the License, or (at your option) any later version. * * 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 "katelayoutcache.h" #include "moc_katelayoutcache.cpp" #include #include "katerenderer.h" #include "kateview.h" #include "katedocument.h" #include "katebuffer.h" static bool enableLayoutCache = false; //BEGIN KateLineLayoutMap KateLineLayoutMap::KateLineLayoutMap() { } KateLineLayoutMap::~KateLineLayoutMap() { } bool lessThan(const KateLineLayoutMap::LineLayoutPair& lhs, const KateLineLayoutMap::LineLayoutPair& rhs) { return lhs.first < rhs.first; } void KateLineLayoutMap::clear() { m_lineLayouts.clear(); } bool KateLineLayoutMap::contains(int i) const { LineLayoutMap::const_iterator it = qBinaryFind(m_lineLayouts.constBegin(), m_lineLayouts.constEnd(), LineLayoutPair(i,KateLineLayoutPtr()), lessThan); return (it != m_lineLayouts.constEnd()); } void KateLineLayoutMap::insert(int realLine, const KateLineLayoutPtr& lineLayoutPtr) { LineLayoutMap::iterator it = qBinaryFind(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine,KateLineLayoutPtr()), lessThan); if (it != m_lineLayouts.end()) { (*it).second = lineLayoutPtr; } else { it = qUpperBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(realLine,KateLineLayoutPtr()), lessThan); m_lineLayouts.insert(it, LineLayoutPair(realLine, lineLayoutPtr)); } } void KateLineLayoutMap::viewWidthIncreased() { LineLayoutMap::iterator it = m_lineLayouts.begin(); for ( ; it != m_lineLayouts.end(); ++it) { if ((*it).second->isValid() && (*it).second->viewLineCount() > 1) (*it).second->invalidateLayout(); } } void KateLineLayoutMap::viewWidthDecreased(int newWidth) { LineLayoutMap::iterator it = m_lineLayouts.begin(); for ( ; it != m_lineLayouts.end(); ++it) { if ((*it).second->isValid() && ((*it).second->viewLineCount() > 1 || (*it).second->width() > newWidth)) (*it).second->invalidateLayout(); } } void KateLineLayoutMap::relayoutLines(int startRealLine, int endRealLine) { LineLayoutMap::iterator start = qLowerBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(startRealLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator end = qUpperBound(start, m_lineLayouts.end(), LineLayoutPair(endRealLine, KateLineLayoutPtr()), lessThan); while (start != end) { (*start).second->setLayoutDirty(); ++start; } } void KateLineLayoutMap::slotEditDone(int fromLine, int toLine, int shiftAmount) { LineLayoutMap::iterator start = qLowerBound(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(fromLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator end = qUpperBound(start, m_lineLayouts.end(), LineLayoutPair(toLine, KateLineLayoutPtr()), lessThan); LineLayoutMap::iterator it; if (shiftAmount != 0) { for (it = end; it != m_lineLayouts.end(); ++it) { (*it).first += shiftAmount; (*it).second->setLine((*it).second->line() + shiftAmount); } for (it = start; it != end; ++it) { (*it).second->clear(); } m_lineLayouts.erase(start, end); } else { for (it = start; it != end; ++it) { (*it).second->setLayoutDirty(); } } } KateLineLayoutPtr& KateLineLayoutMap::operator[](int i) { LineLayoutMap::iterator it = qBinaryFind(m_lineLayouts.begin(), m_lineLayouts.end(), LineLayoutPair(i, KateLineLayoutPtr()), lessThan); return (*it).second; } //END KateLineLayoutMap KateLayoutCache::KateLayoutCache(KateRenderer* renderer, QObject* parent) : QObject(parent) , m_renderer(renderer) , m_startPos(-1,-1) , m_viewWidth(0) , m_wrap(false) , m_acceptDirtyLayouts (false) { Q_ASSERT(m_renderer); /** * connect to all possible editing primitives */ connect(&m_renderer->doc()->buffer(), SIGNAL(lineWrapped(KTextEditor::Cursor)), this, SLOT(wrapLine(KTextEditor::Cursor))); connect(&m_renderer->doc()->buffer(), SIGNAL(lineUnwrapped(int)), this, SLOT(unwrapLine(int))); connect(&m_renderer->doc()->buffer(), SIGNAL(textInserted(KTextEditor::Cursor,QString)), this, SLOT(insertText(KTextEditor::Cursor,QString))); connect(&m_renderer->doc()->buffer(), SIGNAL(textRemoved(KTextEditor::Range,QString)), this, SLOT(removeText(KTextEditor::Range))); } void KateLayoutCache::updateViewCache(const KTextEditor::Cursor& startPos, int newViewLineCount, int viewLinesScrolled) { //kDebug( 13033 ) << startPos << " nvlc " << newViewLineCount << " vls " << viewLinesScrolled; int oldViewLineCount = m_textLayouts.count(); if (newViewLineCount == -1) newViewLineCount = oldViewLineCount; enableLayoutCache = true; int realLine; if (newViewLineCount == -1) realLine = m_renderer->folding().visibleLineToLine(m_renderer->folding().lineToVisibleLine(startPos.line())); else realLine = m_renderer->folding().visibleLineToLine(startPos.line()); int _viewLine = 0; if (wrap()) { // TODO check these assumptions are ok... probably they don't give much speedup anyway? if (startPos == m_startPos && m_textLayouts.count()) { _viewLine = m_textLayouts.first().viewLine(); } else if (viewLinesScrolled > 0 && viewLinesScrolled < m_textLayouts.count()) { _viewLine = m_textLayouts[viewLinesScrolled].viewLine(); } else { KateLineLayoutPtr l = line(realLine); if (l) { Q_ASSERT(l->isValid()); Q_ASSERT(l->length() >= startPos.column() || m_renderer->view()->wrapCursor()); for (; _viewLine < l->viewLineCount(); ++_viewLine) { const KateTextLayout& t = l->viewLine(_viewLine); if (t.startCol() >= startPos.column() || _viewLine == l->viewLineCount() - 1) goto foundViewLine; } // FIXME FIXME need to calculate past-end-of-line position here... Q_ASSERT(false); foundViewLine: Q_ASSERT(true); } } } m_startPos = startPos; // Move the text layouts if we've just scrolled... if (viewLinesScrolled != 0) { // loop backwards if we've just scrolled up... bool forwards = viewLinesScrolled >= 0 ? true : false; for (int z = forwards ? 0 : m_textLayouts.count() - 1; forwards ? (z < m_textLayouts.count()) : (z >= 0); forwards ? z++ : z--) { int oldZ = z + viewLinesScrolled; if (oldZ >= 0 && oldZ < m_textLayouts.count()) m_textLayouts[z] = m_textLayouts[oldZ]; } } // Resize functionality if (newViewLineCount > oldViewLineCount) { m_textLayouts.reserve(newViewLineCount); } else if (newViewLineCount < oldViewLineCount) { /* FIXME reintroduce... check we're not missing any int lastLine = m_textLayouts[newSize - 1].line(); for (int i = oldSize; i < newSize; i++) { const KateTextLayout& layout = m_textLayouts[i]; if (layout.line() > lastLine && !layout.viewLine()) layout.kateLineLayout()->layout()->setCacheEnabled(false); }*/ m_textLayouts.resize(newViewLineCount); } KateLineLayoutPtr l = line(realLine); for (int i = 0; i < newViewLineCount; ++i) { if (!l) { if (i < m_textLayouts.count()) { if (m_textLayouts[i].isValid()) m_textLayouts[i] = KateTextLayout::invalid(); } else { m_textLayouts.append(KateTextLayout::invalid()); } continue; } Q_ASSERT(l->isValid()); Q_ASSERT(_viewLine < l->viewLineCount()); if (i < m_textLayouts.count()) { bool dirty = false; if (m_textLayouts[i].line() != realLine || m_textLayouts[i].viewLine() != _viewLine || (!m_textLayouts[i].isValid() && l->viewLine(_viewLine).isValid())) dirty = true; m_textLayouts[i] = l->viewLine(_viewLine); if (dirty) m_textLayouts[i].setDirty(true); } else { m_textLayouts.append(l->viewLine(_viewLine)); } //kDebug( 13033 ) << "Laid out line " << realLine << " (" << l << "), viewLine " << _viewLine << " (" << m_textLayouts[i].kateLineLayout().data() << ")"; //m_textLayouts[i].debugOutput(); _viewLine++; if (_viewLine > l->viewLineCount() - 1) { int virtualLine = l->virtualLine() + 1; realLine = m_renderer->folding().visibleLineToLine(virtualLine); _viewLine = 0; if (realLine < m_renderer->doc()->lines()) { l = line(realLine, virtualLine); } else { l = 0; } } } enableLayoutCache = false; } KateLineLayoutPtr KateLayoutCache::line( int realLine, int virtualLine ) { if (m_lineLayouts.contains(realLine)) { KateLineLayoutPtr l = m_lineLayouts[realLine]; // ensure line is OK Q_ASSERT (l->line() == realLine); Q_ASSERT (realLine < m_renderer->doc()->buffer().lines()); if (virtualLine != -1) l->setVirtualLine(virtualLine); if (!l->isValid()) { l->setUsePlainTextLine (acceptDirtyLayouts()); l->textLine (!acceptDirtyLayouts()); m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); } else if (l->isLayoutDirty() && !acceptDirtyLayouts()) { // reset textline l->setUsePlainTextLine (false); l->textLine (true); m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); } Q_ASSERT(l->isValid() && (!l->isLayoutDirty() || acceptDirtyLayouts())); return l; } if (realLine < 0 || realLine >= m_renderer->doc()->lines()) return KateLineLayoutPtr(); KateLineLayoutPtr l(new KateLineLayout(*m_renderer)); l->setLine(realLine, virtualLine); // Mark it dirty, because it may not have the syntax highlighting applied // mark this here, to allow layoutLine to use plainLines... if (acceptDirtyLayouts()) l->setUsePlainTextLine (true); m_renderer->layoutLine(l, wrap() ? m_viewWidth : -1, enableLayoutCache); Q_ASSERT(l->isValid()); if (acceptDirtyLayouts()) l->setLayoutDirty (true); m_lineLayouts.insert(realLine, l); return l; } KateLineLayoutPtr KateLayoutCache::line( const KTextEditor::Cursor & realCursor ) { return line(realCursor.line()); } KateTextLayout KateLayoutCache::textLayout( const KTextEditor::Cursor & realCursor ) { /*if (realCursor >= viewCacheStart() && (realCursor < viewCacheEnd() || realCursor == viewCacheEnd() && !m_textLayouts.last().wrap())) foreach (const KateTextLayout& l, m_textLayouts) if (l.line() == realCursor.line() && (l.endCol() < realCursor.column() || !l.wrap())) return l;*/ return line(realCursor.line())->viewLine(viewLine(realCursor)); } KateTextLayout KateLayoutCache::textLayout( uint realLine, int _viewLine ) { /*if (m_textLayouts.count() && (realLine >= m_textLayouts.first().line() && _viewLine >= m_textLayouts.first().viewLine()) && (realLine <= m_textLayouts.last().line() && _viewLine <= m_textLayouts.first().viewLine())) foreach (const KateTextLayout& l, m_textLayouts) if (l.line() == realLine && l.viewLine() == _viewLine) return const_cast(l);*/ return line(realLine)->viewLine(_viewLine); } KateTextLayout & KateLayoutCache::viewLine( int _viewLine ) { Q_ASSERT(_viewLine >= 0 && _viewLine < m_textLayouts.count()); return m_textLayouts[_viewLine]; } int KateLayoutCache::viewCacheLineCount( ) const { return m_textLayouts.count(); } KTextEditor::Cursor KateLayoutCache::viewCacheStart( ) const { return m_textLayouts.count() ? m_textLayouts.first().start() : KTextEditor::Cursor(); } KTextEditor::Cursor KateLayoutCache::viewCacheEnd( ) const { return m_textLayouts.count() ? m_textLayouts.last().end() : KTextEditor::Cursor(); } int KateLayoutCache::viewWidth( ) const { return m_viewWidth; } /** * This returns the view line upon which realCursor is situated. * The view line is the number of lines in the view from the first line * The supplied cursor should be in real lines. */ int KateLayoutCache::viewLine(const KTextEditor::Cursor& realCursor) { if (realCursor.column() <= 0 || realCursor.line() < 0) return 0; Q_ASSERT(realCursor.line() < m_renderer->doc()->lines()); KateLineLayoutPtr thisLine = line(realCursor.line()); for (int i = 0; i < thisLine->viewLineCount(); ++i) { const KateTextLayout& l = thisLine->viewLine(i); if (realCursor.column() >= l.startCol() && realCursor.column() < l.endCol()) return i; } return thisLine->viewLineCount() - 1; } int KateLayoutCache::displayViewLine(const KTextEditor::Cursor& virtualCursor, bool limitToVisible) { if (!virtualCursor.isValid()) return -1; KTextEditor::Cursor work = viewCacheStart(); // only try this with valid lines! if (work.isValid()) work.setLine(m_renderer->folding().lineToVisibleLine(work.line())); if (!work.isValid()) return virtualCursor.line(); int limit = m_textLayouts.count(); // Efficient non-word-wrapped path if (!m_renderer->view()->dynWordWrap()) { int ret = virtualCursor.line() - work.line(); if (limitToVisible && (ret < 0 || ret > limit)) return -1; else return ret; } if (work == virtualCursor) { return 0; } int ret = -(int)viewLine(viewCacheStart()); bool forwards = (work < virtualCursor); // FIXME switch to using ranges? faster? if (forwards) { while (work.line() != virtualCursor.line()) { ret += viewLineCount(m_renderer->folding().visibleLineToLine(work.line())); work.setLine(work.line() + 1); if (limitToVisible && ret > limit) return -1; } } else { while (work.line() != virtualCursor.line()) { work.setLine(work.line() - 1); ret -= viewLineCount(m_renderer->folding().visibleLineToLine(work.line())); if (limitToVisible && ret < 0) return -1; } } // final difference KTextEditor::Cursor realCursor = virtualCursor; realCursor.setLine(m_renderer->folding().visibleLineToLine(realCursor.line())); if (realCursor.column() == -1) realCursor.setColumn(m_renderer->doc()->lineLength(realCursor.line())); ret += viewLine(realCursor); if (limitToVisible && (ret < 0 || ret > limit)) return -1; return ret; } int KateLayoutCache::lastViewLine(int realLine) { if (!m_renderer->view()->dynWordWrap()) return 0; KateLineLayoutPtr l = line(realLine); Q_ASSERT(l); return l->viewLineCount() - 1; } int KateLayoutCache::viewLineCount(int realLine) { return lastViewLine(realLine) + 1; } void KateLayoutCache::viewCacheDebugOutput( ) const { kDebug( 13033 ) << "Printing values for " << m_textLayouts.count() << " lines:"; if (m_textLayouts.count()) { foreach (const KateTextLayout& t, m_textLayouts) if (t.isValid()) { t.debugOutput(); } else { kDebug( 13033 ) << "Line Invalid."; } } } void KateLayoutCache::wrapLine (const KTextEditor::Cursor &position) { m_lineLayouts.slotEditDone (position.line(), position.line() + 1, 1); } void KateLayoutCache::unwrapLine (int line) { m_lineLayouts.slotEditDone (line - 1, line, -1); } void KateLayoutCache::insertText (const KTextEditor::Cursor &position, const QString &) { m_lineLayouts.slotEditDone(position.line(), position.line(), 0); } void KateLayoutCache::removeText (const KTextEditor::Range &range) { m_lineLayouts.slotEditDone(range.start().line(), range.start().line(), 0); } void KateLayoutCache::clear( ) { m_textLayouts.clear(); m_lineLayouts.clear(); m_startPos = KTextEditor::Cursor(-1,-1); } void KateLayoutCache::setViewWidth( int width ) { bool wider = width > m_viewWidth; m_viewWidth = width; m_lineLayouts.clear(); m_startPos = KTextEditor::Cursor(-1,-1); // Only get rid of layouts that we have to if (wider) { m_lineLayouts.viewWidthIncreased(); } else { m_lineLayouts.viewWidthDecreased(width); } } bool KateLayoutCache::wrap( ) const { return m_wrap; } void KateLayoutCache::setWrap( bool wrap ) { m_wrap = wrap; clear(); } void KateLayoutCache::relayoutLines( int startRealLine, int endRealLine ) { if (startRealLine > endRealLine) kWarning() << "start" << startRealLine << "before end" << endRealLine; m_lineLayouts.relayoutLines(startRealLine, endRealLine); } bool KateLayoutCache::acceptDirtyLayouts() { return m_acceptDirtyLayouts; } void KateLayoutCache::setAcceptDirtyLayouts(bool accept) { m_acceptDirtyLayouts = accept; } // kate: space-indent on; indent-width 2; replace-tabs on;