/* * BufferHandler.java * :tabSize=4:indentSize=4:noTabs=false: * :folding=explicit:collapseFolds=1: * * Copyright (C) 2001, 2005 Slava Pestov * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version 2 * of the License, or any later version. * * This program 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 General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ package org.gjt.sp.jedit.textarea; import java.util.Iterator; import org.gjt.sp.jedit.buffer.*; import org.gjt.sp.jedit.Debug; /** * Note that in this class we take great care to defer complicated * calculations to the end of the current transaction if the buffer * informs us a compound edit is in progress * (<code>isTransactionInProgress()</code>). * * This greatly speeds up replace all for example, by only doing certain * things once, particularly in <code>moveCaretPosition()</code>. * * Try doing a replace all in a large file, for example. It is very slow * in 3.2, faster in 4.0 (where the transaction optimization was * introduced) and faster still in 4.1 (where it was further improved). * * There is still work to do; see TODO.txt. */ class BufferHandler implements BufferListener { private final DisplayManager displayManager; private final TextArea textArea; private final JEditBuffer buffer; private boolean delayedUpdate; private boolean delayedMultilineUpdate; private int delayedUpdateStart; private int delayedUpdateEnd; //{{{ BufferChangeHandler constructor BufferHandler(DisplayManager displayManager, TextArea textArea, JEditBuffer buffer) { this.displayManager = displayManager; this.textArea = textArea; this.buffer = buffer; } //}}} //{{{ bufferLoaded() method public void bufferLoaded(JEditBuffer buffer) { displayManager.bufferLoaded(); textArea._finishCaretUpdate(); } //}}} //{{{ foldHandlerChanged() method public void foldHandlerChanged(JEditBuffer buffer) { displayManager.foldHandlerChanged(); } //}}} //{{{ foldLevelChanged() method public void foldLevelChanged(JEditBuffer buffer, int start, int end) { //System.err.println("foldLevelChanged " + (start-1) + " to " + textArea.getLastPhysicalLine() + "," + end); if(textArea.getDisplayManager() == displayManager && end != 0 && !buffer.isLoading()) { textArea.invalidateLineRange(start - 1, textArea.getLastPhysicalLine()); } } //}}} //{{{ contentInserted() method public void contentInserted(JEditBuffer buffer, int startLine, int offset, int numLines, int length) { if(buffer.isLoading()) return; displayManager.screenLineMgr.contentInserted(startLine,numLines); int endLine = startLine + numLines; if(numLines != 0) delayedMultilineUpdate = true; displayManager.folds.contentInserted(startLine,numLines); FirstLine firstLine = displayManager.firstLine; ScrollLineCount scrollLineCount = displayManager.scrollLineCount; if(textArea.getDisplayManager() == displayManager) { firstLine.contentInserted(startLine,numLines); scrollLineCount.contentInserted(startLine,numLines); if(delayedUpdateEnd >= startLine) delayedUpdateEnd += numLines; delayUpdate(startLine,endLine); //{{{ resize selections if necessary Iterator<Selection> iter = textArea.getSelectionIterator(); while(iter.hasNext()) { Selection s = iter.next(); if(s.contentInserted(buffer,startLine,offset, numLines,length)) { delayUpdate(s.startLine,s.endLine); } } //}}} int caret = textArea.getCaretPosition(); if(caret >= offset) { int scrollMode = textArea.caretAutoScroll() ? TextArea.ELECTRIC_SCROLL : TextArea.NO_SCROLL; textArea.moveCaretPosition( caret + length,scrollMode); } else { int scrollMode = textArea.caretAutoScroll() ? TextArea.NORMAL_SCROLL : TextArea.NO_SCROLL; textArea.moveCaretPosition( caret,scrollMode); } } else { firstLine.setCallReset(true); scrollLineCount.setCallReset(true); } } //}}} //{{{ preContentInserted() method /** * Called when text is about to be inserted in the buffer. * @param buffer The buffer in question * @param startLine The first line * @param offset The start offset, from the beginning of the buffer * @param numLines The number of lines inserted * @param length The number of characters inserted * @since jEdit 4.3pre11 */ public void preContentInserted(JEditBuffer buffer, int startLine, int offset, int numLines, int length) { if(buffer.isLoading()) return; if(textArea.getDisplayManager() == displayManager) { getReadyToBreakFold(startLine); displayManager.firstLine.preContentInserted(startLine, numLines); displayManager.scrollLineCount.preContentInserted(startLine, numLines); } } //}}} //{{{ preContentRemoved() method /** * Called when text is about to be removed from the buffer, but is * still present. * @param buffer The buffer in question * @param startLine The first line * @param offset The start offset, from the beginning of the buffer * @param numLines The number of lines to be removed * @param length The number of characters to be removed * @since jEdit 4.3pre3 */ public void preContentRemoved(JEditBuffer buffer, int startLine, int offset, int numLines, int length) { if(buffer.isLoading()) return; FirstLine firstLine = displayManager.firstLine; ScrollLineCount scrollLineCount = displayManager.scrollLineCount; if(textArea.getDisplayManager() == displayManager) { if(numLines == 0) { getReadyToBreakFold(startLine); } else { int lastLine = startLine + numLines; if( !displayManager.isLineVisible(startLine) || !displayManager.isLineVisible(lastLine) || offset != buffer.getLineStartOffset(startLine) || offset + length != buffer.getLineStartOffset(lastLine)) { getReadyToBreakFold(startLine); getReadyToBreakFold(lastLine); } else { // The removal will not touch // inside of folds and will not // modify any remaining lines. } } firstLine.preContentRemoved(startLine,offset, numLines); scrollLineCount.preContentRemoved(startLine, offset, numLines); if(delayedUpdateEnd >= startLine) delayedUpdateEnd -= numLines; delayUpdate(startLine,startLine); } else { firstLine.setCallReset(true); scrollLineCount.setCallReset(true); } displayManager.screenLineMgr.contentRemoved(startLine,numLines); if(numLines == 0) return; delayedMultilineUpdate = true; if(displayManager.folds.preContentRemoved(startLine,numLines)) { displayManager.folds.reset(buffer.getLineCount()); firstLine.setCallReset(true); scrollLineCount.setCallReset(true); } if(firstLine.getPhysicalLine() > displayManager.getLastVisibleLine() || firstLine.getPhysicalLine() < displayManager.getFirstVisibleLine() ) { // will be handled later. // see comments at the end of // transactionComplete(). } } //}}} //{{{ contentRemoved() method public void contentRemoved(JEditBuffer buffer, int startLine, int start, int numLines, int length) { if(buffer.isLoading()) return; FirstLine firstLine = displayManager.firstLine; ScrollLineCount scrollLineCount = displayManager.scrollLineCount; if(textArea.getDisplayManager() == displayManager) { firstLine.contentRemoved(startLine,start,numLines); scrollLineCount.contentRemoved(startLine,start,numLines); //{{{ resize selections if necessary int nSel = textArea.getSelectionCount(); Iterator<Selection> iter = textArea.getSelectionIterator(); while(iter.hasNext()) { Selection s = iter.next(); if(s.contentRemoved(buffer,startLine, start,numLines,length)) { delayUpdate(s.startLine,s.endLine); if(nSel == 1 && s.start == s.end) iter.remove(); } } //}}} int caret = textArea.getCaretPosition(); if(caret >= start + length) { int scrollMode = textArea.caretAutoScroll() ? TextArea.ELECTRIC_SCROLL : TextArea.NO_SCROLL; textArea.moveCaretPosition( caret - length, scrollMode); } else if(caret >= start) { int scrollMode = textArea.caretAutoScroll() ? TextArea.ELECTRIC_SCROLL : TextArea.NO_SCROLL; textArea.moveCaretPosition( start,scrollMode); } else { int scrollMode = textArea.caretAutoScroll() ? TextArea.NORMAL_SCROLL : TextArea.NO_SCROLL; textArea.moveCaretPosition(caret,scrollMode); } } } //}}} //{{{ transactionComplete() method public void transactionComplete(JEditBuffer buffer) { if(textArea.getDisplayManager() != displayManager) { delayedUpdate = false; return; } if(delayedUpdate) doDelayedUpdate(); textArea._finishCaretUpdate(); delayedUpdate = false; //{{{ Debug code if(Debug.SCROLL_VERIFY) { int line = delayedUpdateStart; if(!displayManager.isLineVisible(line)) line = displayManager.getNextVisibleLine(line); System.err.println(delayedUpdateStart + ":" + delayedUpdateEnd + ':' + textArea.getLineCount()); int scrollLineCount = 0; while(line != -1 && line <= delayedUpdateEnd) { scrollLineCount += displayManager.getScreenLineCount(line); line = displayManager.getNextVisibleLine(line); } if(scrollLineCount != displayManager.getScrollLineCount()) { throw new InternalError(scrollLineCount + " != " + displayManager.getScrollLineCount()); } } //}}} } //}}} //{{{ doDelayedUpdate() method private void doDelayedUpdate() { // must be done before the below call // so that the chunk cache is not // updated with an invisible first // line (see above) displayManager.notifyScreenLineChanges(); if(delayedMultilineUpdate) { textArea.invalidateScreenLineRange( textArea.chunkCache .getScreenLineOfOffset( delayedUpdateStart,0), textArea.getVisibleLines()); delayedMultilineUpdate = false; } else { textArea.invalidateLineRange( delayedUpdateStart, delayedUpdateEnd); } // update visible lines int visibleLines = textArea.getVisibleLines(); if(visibleLines != 0) { textArea.chunkCache.getLineInfo( visibleLines - 1); } // force the fold levels to be // updated. // when painting the last line of // a buffer, Buffer.isFoldStart() // doesn't call getFoldLevel(), // hence the foldLevelChanged() // event might not be sent for the // previous line. buffer.getFoldLevel(delayedUpdateEnd); } //}}} //{{{ delayUpdate() method private void delayUpdate(int startLine, int endLine) { textArea.chunkCache.invalidateChunksFromPhys(startLine); if(!delayedUpdate) { delayedUpdateStart = startLine; delayedUpdateEnd = endLine; delayedUpdate = true; } else { delayedUpdateStart = Math.min( delayedUpdateStart, startLine); delayedUpdateEnd = Math.max( delayedUpdateEnd, endLine); } } //}}} //{{{ getReadyToBreakFold() method // This is a fix for black hole bug. // If you modify a part of folded lines, like {{{ (followed by }}}), // the fold is removed so it must be expanded otherwise the text // remains invisible. private void getReadyToBreakFold(int line) { displayManager.expandFold(line, false); } //}}} }