PageRenderTime 276ms CodeModel.GetById 213ms app.highlight 53ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-2-pre14/org/gjt/sp/jedit/textarea/DisplayManager.java

#
Java | 1942 lines | 1404 code | 219 blank | 319 comment | 340 complexity | 6a1ec83ce4117daa766edaf3e251c515 MD5 | raw file
   1/*
   2 * DisplayManager.java - Low-level text display
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 2001, 2004 Slava Pestov
   7 *
   8 * This program is free software; you can redistribute it and/or
   9 * modify it under the terms of the GNU General Public License
  10 * as published by the Free Software Foundation; either version 2
  11 * of the License, or any later version.
  12 *
  13 * This program is distributed in the hope that it will be useful,
  14 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  16 * GNU General Public License for more details.
  17 *
  18 * You should have received a copy of the GNU General Public License
  19 * along with this program; if not, write to the Free Software
  20 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  21 */
  22
  23package org.gjt.sp.jedit.textarea;
  24
  25//{{{ Imports
  26import java.awt.Toolkit;
  27import java.util.*;
  28import org.gjt.sp.jedit.buffer.*;
  29import org.gjt.sp.jedit.*;
  30import org.gjt.sp.util.Log;
  31//}}}
  32
  33/**
  34 * Manages low-level text display tasks.
  35 * @since jEdit 4.2pre1
  36 * @author Slava Pestov
  37 * @version $Id: DisplayManager.java 5057 2004-06-03 21:11:08Z spestov $
  38 */
  39public class DisplayManager
  40{
  41	//{{{ Static part
  42
  43	public static long scanCount, scannedLines;
  44
  45	//{{{ getDisplayManager() method
  46	static DisplayManager getDisplayManager(Buffer buffer,
  47		JEditTextArea textArea)
  48	{
  49		List l = (List)bufferMap.get(buffer);
  50		DisplayManager dmgr;
  51		if(l == null)
  52		{
  53			l = new LinkedList();
  54			bufferMap.put(buffer,l);
  55		}
  56
  57		Iterator liter = l.iterator();
  58		while(liter.hasNext())
  59		{
  60			dmgr = (DisplayManager)liter.next();
  61			if(!dmgr.inUse && dmgr.textArea == textArea)
  62			{
  63				dmgr.inUse = true;
  64				return dmgr;
  65			}
  66		}
  67
  68		// if we got here, no unused display manager in list
  69		dmgr = new DisplayManager(buffer,textArea);
  70		dmgr.inUse = true;
  71		l.add(dmgr);
  72
  73		return dmgr;
  74	} //}}}
  75
  76	//{{{ releaseDisplayManager() method
  77	static void releaseDisplayManager(DisplayManager dmgr)
  78	{
  79		dmgr.inUse = false;
  80	} //}}}
  81
  82	//{{{ bufferClosed() method
  83	public static void bufferClosed(Buffer buffer)
  84	{
  85		bufferMap.remove(buffer);
  86	} //}}}
  87
  88	//{{{ textAreaDisposed() method
  89	static void textAreaDisposed(JEditTextArea textArea)
  90	{
  91		Iterator biter = bufferMap.values().iterator();
  92		while(biter.hasNext())
  93		{
  94			List l = (List)biter.next();
  95			Iterator liter = l.iterator();
  96			while(liter.hasNext())
  97			{
  98				DisplayManager dmgr = (DisplayManager)
  99					liter.next();
 100				if(dmgr.textArea == textArea)
 101				{
 102					dmgr.dispose();
 103					liter.remove();
 104				}
 105			}
 106		}
 107	} //}}}
 108
 109	//{{{ _notifyScreenLineChanges() method
 110	/* static void _notifyScreenLineChanges(Buffer buffer)
 111	{
 112		Iterator iter = ((List)bufferMap.get(buffer)).iterator();
 113		while(iter.hasNext())
 114		{
 115			((DisplayManager)iter.next())._notifyScreenLineChanges();
 116		}
 117	} */ //}}}
 118
 119	private static Map bufferMap = new HashMap();
 120	//}}}
 121
 122	//{{{ isLineVisible() method
 123	/**
 124	 * Returns if the specified line is visible.
 125	 * @param line A physical line index
 126	 * @since jEdit 4.2pre1
 127	 */
 128	public final boolean isLineVisible(int line)
 129	{
 130		return fvmget(line) % 2 == 0;
 131	} //}}}
 132
 133	//{{{ getFirstVisibleLine() method
 134	/**
 135	 * Returns the physical line number of the first visible line.
 136	 * @since jEdit 4.2pre1
 137	 */
 138	public int getFirstVisibleLine()
 139	{
 140		return fvm[0];
 141	} //}}}
 142
 143	//{{{ getLastVisibleLine() method
 144	/**
 145	 * Returns the physical line number of the last visible line.
 146	 * @since jEdit 4.2pre1
 147	 */
 148	public int getLastVisibleLine()
 149	{
 150		return fvm[fvmcount - 1] - 1;
 151	} //}}}
 152
 153	//{{{ getNextVisibleLine() method
 154	/**
 155	 * Returns the next visible line after the specified line index.
 156	 * @param line A physical line index
 157	 * @since jEdit 4.0pre1
 158	 */
 159	public int getNextVisibleLine(int line)
 160	{
 161		if(line < 0 || line >= buffer.getLineCount())
 162			throw new ArrayIndexOutOfBoundsException(line);
 163
 164		int index = fvmget(line);
 165		/* in collapsed range */
 166		if(index % 2 != 0)
 167		{
 168			/* beyond last visible line */
 169			if(fvmcount == index + 1)
 170				return - 1;
 171			/* start of next expanded range */
 172			else
 173				return fvm[index + 1];
 174		}
 175		/* last in expanded range */
 176		else if(line == fvm[index + 1] - 1)
 177		{
 178			/* equal to last visible line */
 179			if(fvmcount == index + 2)
 180				return -1;
 181			/* start of next expanded range */
 182			else
 183				return fvm[index + 2];
 184		}
 185		/* next in expanded range */
 186		else
 187			return line + 1;
 188	} //}}}
 189
 190	//{{{ getPrevVisibleLine() method
 191	/**
 192	 * Returns the previous visible line before the specified line index.
 193	 * @param line A physical line index
 194	 * @since jEdit 4.0pre1
 195	 */
 196	public int getPrevVisibleLine(int line)
 197	{
 198		if(line < 0 || line >= buffer.getLineCount())
 199			throw new ArrayIndexOutOfBoundsException(line);
 200
 201		int index = fvmget(line);
 202		/* before first visible line */
 203		if(index == -1)
 204			return -1;
 205		/* in collapsed range */
 206		else if(index % 2 == 1)
 207		{
 208			/* end of prev expanded range */
 209			return fvm[index] - 1;
 210		}
 211		/* first in expanded range */
 212		else if(line == fvm[index])
 213		{
 214			/* equal to first visible line */
 215			if(index == 0)
 216				return -1;
 217			/* end of prev expanded range */
 218			else
 219				return fvm[index - 1] - 1;
 220		}
 221		/* prev in expanded range */
 222		else
 223			return line - 1;
 224	} //}}}
 225
 226	//{{{ getScreenLineCount() method
 227	public final int getScreenLineCount(int line)
 228	{
 229		if(lineMgr.isScreenLineCountValid(line))
 230			return lineMgr.getScreenLineCount(line);
 231		else
 232		{
 233			int newCount = textArea.chunkCache
 234				.getLineSubregionCount(line);
 235
 236			setScreenLineCount(line,newCount);
 237			return newCount;
 238		}
 239	} //}}}
 240
 241	//{{{ getScrollLineCount() method
 242	public final int getScrollLineCount()
 243	{
 244		return scrollLineCount.scrollLine;
 245	} //}}}
 246
 247	//{{{ collapseFold() method
 248	/**
 249	 * Collapses the fold at the specified physical line index.
 250	 * @param line A physical line index
 251	 * @since jEdit 4.2pre1
 252	 */
 253	public void collapseFold(int line)
 254	{
 255		int lineCount = buffer.getLineCount();
 256		int start = 0;
 257		int end = lineCount - 1;
 258
 259		// if the caret is on a collapsed fold, collapse the
 260		// parent fold
 261		if(line != 0
 262			&& line != buffer.getLineCount() - 1
 263			&& buffer.isFoldStart(line)
 264			&& !isLineVisible(line + 1))
 265		{
 266			line--;
 267		}
 268
 269		int initialFoldLevel = buffer.getFoldLevel(line);
 270
 271		//{{{ Find fold start and end...
 272		if(line != lineCount - 1
 273			&& buffer.getFoldLevel(line + 1) > initialFoldLevel)
 274		{
 275			// this line is the start of a fold
 276			start = line + 1;
 277
 278			for(int i = line + 1; i < lineCount; i++)
 279			{
 280				if(buffer.getFoldLevel(i) <= initialFoldLevel)
 281				{
 282					end = i - 1;
 283					break;
 284				}
 285			}
 286		}
 287		else
 288		{
 289			boolean ok = false;
 290
 291			// scan backwards looking for the start
 292			for(int i = line - 1; i >= 0; i--)
 293			{
 294				if(buffer.getFoldLevel(i) < initialFoldLevel)
 295				{
 296					start = i + 1;
 297					ok = true;
 298					break;
 299				}
 300			}
 301
 302			if(!ok)
 303			{
 304				// no folds in buffer
 305				return;
 306			}
 307
 308			for(int i = line + 1; i < lineCount; i++)
 309			{
 310				if(buffer.getFoldLevel(i) < initialFoldLevel)
 311				{
 312					end = i - 1;
 313					break;
 314				}
 315			}
 316		} //}}}
 317
 318		// Collapse the fold...
 319		hideLineRange(start,end);
 320
 321		_notifyScreenLineChanges();
 322		textArea.foldStructureChanged();
 323	} //}}}
 324
 325	//{{{ expandFold() method
 326	/**
 327	 * Expands the fold at the specified physical line index.
 328	 * @param line A physical line index
 329	 * @param fully If true, all subfolds will also be expanded
 330	 * @since jEdit 4.2pre1
 331	 */
 332	public int expandFold(int line, boolean fully)
 333	{
 334		// the first sub-fold. used by JEditTextArea.expandFold().
 335		int returnValue = -1;
 336
 337		int lineCount = buffer.getLineCount();
 338		int start = 0;
 339		int end = lineCount - 1;
 340
 341		int initialFoldLevel = buffer.getFoldLevel(line);
 342
 343		//{{{ Find fold start and fold end...
 344		if(line != lineCount - 1
 345			&& isLineVisible(line)
 346			&& !isLineVisible(line + 1)
 347			&& buffer.getFoldLevel(line + 1) > initialFoldLevel)
 348		{
 349			// this line is the start of a fold
 350
 351			int index = fvmget(line + 1);
 352			if(index == -1)
 353			{
 354				expandAllFolds();
 355				return -1;
 356			}
 357
 358			start = fvm[index];
 359			if(index != fvmcount - 1)
 360				end = fvm[index + 1] - 1;
 361			else
 362			{
 363				start = line + 1;
 364
 365				for(int i = line + 1; i < lineCount; i++)
 366				{
 367					if(/* isLineVisible(i) && */
 368						buffer.getFoldLevel(i) <= initialFoldLevel)
 369					{
 370						end = i - 1;
 371						break;
 372					}
 373				}
 374			}
 375		}
 376		else
 377		{
 378			int index = fvmget(line);
 379			if(index == -1)
 380			{
 381				expandAllFolds();
 382				return -1;
 383			}
 384
 385			start = fvm[index];
 386			if(index != fvmcount - 1)
 387				end = fvm[index + 1] - 1;
 388			else
 389			{
 390				for(int i = line + 1; i < lineCount; i++)
 391				{
 392					//XXX
 393					if((isLineVisible(i) &&
 394						buffer.getFoldLevel(i) < initialFoldLevel)
 395						|| i == getLastVisibleLine())
 396					{
 397						end = i - 1;
 398						break;
 399					}
 400				}
 401			}
 402		} //}}}
 403
 404		//{{{ Expand the fold...
 405		if(fully)
 406		{
 407			showLineRange(start,end);
 408		}
 409		else
 410		{
 411			// we need a different value of initialFoldLevel here!
 412			initialFoldLevel = buffer.getFoldLevel(start);
 413
 414			int firstVisible = start;
 415
 416			for(int i = start; i <= end; i++)
 417			{
 418				if(buffer.getFoldLevel(i) > initialFoldLevel)
 419				{
 420					if(returnValue == -1
 421						&& i != 0
 422						&& buffer.isFoldStart(i - 1))
 423					{
 424						returnValue = i - 1;
 425					}
 426
 427					if(firstVisible != i)
 428					{
 429						showLineRange(firstVisible,i - 1);
 430					}
 431					firstVisible = i + 1;
 432				}
 433			}
 434
 435			if(firstVisible != end + 1)
 436				showLineRange(firstVisible,end);
 437
 438			if(!isLineVisible(line))
 439			{
 440				// this is a hack, and really needs to be done better.
 441				expandFold(line,false);
 442				return returnValue;
 443			}
 444		} //}}}
 445
 446		_notifyScreenLineChanges();
 447		textArea.foldStructureChanged();
 448
 449		return returnValue;
 450	} //}}}
 451
 452	//{{{ expandAllFolds() method
 453	/**
 454	 * Expands all folds.
 455	 * @since jEdit 4.2pre1
 456	 */
 457	public void expandAllFolds()
 458	{
 459		showLineRange(0,buffer.getLineCount() - 1);
 460		_notifyScreenLineChanges();
 461		textArea.foldStructureChanged();
 462	} //}}}
 463
 464	//{{{ expandFolds() method
 465	/**
 466	 * This method should only be called from <code>actions.xml</code>.
 467	 * @since jEdit 4.2pre1
 468	 */
 469	public void expandFolds(char digit)
 470	{
 471		if(digit < '1' || digit > '9')
 472		{
 473			Toolkit.getDefaultToolkit().beep();
 474			return;
 475		}
 476		else
 477			expandFolds((int)(digit - '1') + 1);
 478	} //}}}
 479
 480	//{{{ expandFolds() method
 481	/**
 482	 * Expands all folds with the specified fold level.
 483	 * @param foldLevel The fold level
 484	 * @since jEdit 4.2pre1
 485	 */
 486	public void expandFolds(int foldLevel)
 487	{
 488		if(buffer.getFoldHandler() instanceof IndentFoldHandler)
 489			foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
 490
 491		showLineRange(0,buffer.getLineCount() - 1);
 492
 493		/* this ensures that the first line is always visible */
 494		boolean seenVisibleLine = false;
 495
 496		int firstInvisible = 0;
 497
 498		for(int i = 0; i < buffer.getLineCount(); i++)
 499		{
 500			if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
 501			{
 502				if(firstInvisible != i)
 503				{
 504					hideLineRange(firstInvisible,
 505						i - 1);
 506				}
 507				firstInvisible = i + 1;
 508				seenVisibleLine = true;
 509			}
 510		}
 511
 512		if(firstInvisible != buffer.getLineCount())
 513			hideLineRange(firstInvisible,buffer.getLineCount() - 1);
 514
 515		_notifyScreenLineChanges();
 516		textArea.foldStructureChanged();
 517	} //}}}
 518
 519	//{{{ narrow() method
 520	/**
 521	 * Narrows the visible portion of the buffer to the specified
 522	 * line range.
 523	 * @param start The first line
 524	 * @param end The last line
 525	 * @since jEdit 4.2pre1
 526	 */
 527	public void narrow(int start, int end)
 528	{
 529		if(start > end || start < 0 || end >= buffer.getLineCount())
 530			throw new ArrayIndexOutOfBoundsException(start + ", " + end);
 531
 532		if(start < getFirstVisibleLine() || end > getLastVisibleLine())
 533			expandAllFolds();
 534
 535		if(start != 0)
 536			hideLineRange(0,start - 1);
 537		if(end != buffer.getLineCount() - 1)
 538			hideLineRange(end + 1,buffer.getLineCount() - 1);
 539
 540		// if we narrowed to a single collapsed fold
 541		if(start != buffer.getLineCount() - 1
 542			&& !isLineVisible(start + 1))
 543			expandFold(start,false);
 544
 545		// Hack... need a more direct way of obtaining a view?
 546		// JEditTextArea.getView() method?
 547		GUIUtilities.getView(textArea).getStatus().setMessageAndClear(
 548			jEdit.getProperty("view.status.narrow"));
 549
 550		_notifyScreenLineChanges();
 551		textArea.foldStructureChanged();
 552	} //}}}
 553
 554	//{{{ Package-private members
 555	boolean softWrap;
 556	int wrapMargin;
 557	FirstLine firstLine;
 558	ScrollLineCount scrollLineCount;
 559
 560	//{{{ init() method
 561	void init()
 562	{
 563		if(!initialized)
 564		{
 565			initialized = true;
 566			fvm = new int[2];
 567			if(buffer.isLoaded())
 568				bufferChangeHandler.foldHandlerChanged(buffer);
 569			else
 570				fvmreset();
 571			_notifyScreenLineChanges();
 572		}
 573		else
 574		{
 575			updateWrapSettings();
 576			if(buffer.isLoaded())
 577			{
 578				_notifyScreenLineChanges();
 579				textArea.updateScrollBars();
 580				textArea.recalculateLastPhysicalLine();
 581			}
 582		}
 583	} //}}}
 584
 585	//{{{ setScreenLineCount() method
 586	/**
 587	 * Sets the number of screen lines that the specified physical line
 588	 * is split into.
 589	 * @since jEdit 4.2pre1
 590	 */
 591	void setScreenLineCount(int line, int count)
 592	{
 593		int oldCount = lineMgr.getScreenLineCount(line);
 594		// still have to call this even if it equals the
 595		// old one so that the offset manager sets the
 596		// validity flag!
 597		lineMgr.setScreenLineCount(line,count);
 598		// this notifies each display manager editing this
 599		// buffer of the screen line count change
 600		if(count != oldCount)
 601		{
 602			Iterator iter = ((List)bufferMap.get(buffer))
 603				.iterator();
 604			while(iter.hasNext())
 605			{
 606				((DisplayManager)iter.next())._setScreenLineCount(
 607					line,oldCount,count);
 608			}
 609		}
 610	} //}}}
 611
 612	//{{{ updateWrapSettings() method
 613	void updateWrapSettings()
 614	{
 615		String wrap = buffer.getStringProperty("wrap");
 616		softWrap = wrap.equals("soft");
 617		if(textArea.maxLineLen <= 0)
 618		{
 619			softWrap = false;
 620			wrapMargin = 0;
 621		}
 622		else
 623		{
 624			// stupidity
 625			char[] foo = new char[textArea.maxLineLen];
 626			for(int i = 0; i < foo.length; i++)
 627			{
 628				foo[i] = ' ';
 629			}
 630			TextAreaPainter painter = textArea.getPainter();
 631			wrapMargin = (int)painter.getFont().getStringBounds(
 632				foo,0,foo.length,
 633				painter.getFontRenderContext())
 634				.getWidth();
 635		}
 636	} //}}}
 637
 638	//{{{ _notifyScreenLineChanges() method
 639	void _notifyScreenLineChanges()
 640	{
 641		if(Debug.SCROLL_DEBUG)
 642			Log.log(Log.DEBUG,this,"_notifyScreenLineChanges()");
 643
 644		// when the text area switches to us, it will do
 645		// a reset anyway
 646		if(textArea.getDisplayManager() == this)
 647		{
 648			try
 649			{
 650				if(firstLine.callReset)
 651					firstLine.reset();
 652				else if(firstLine.callChanged)
 653					firstLine.changed();
 654
 655				if(scrollLineCount.callReset)
 656					scrollLineCount.reset();
 657				else if(scrollLineCount.callChanged)
 658					scrollLineCount.changed();
 659			}
 660			finally
 661			{
 662				firstLine.callReset = firstLine.callChanged = false;
 663				scrollLineCount.callReset = scrollLineCount.callChanged = false;
 664			}
 665		}
 666	} //}}}
 667
 668	//}}}
 669
 670	//{{{ Private members
 671	private boolean initialized;
 672	private boolean inUse;
 673	private Buffer buffer;
 674	private LineManager lineMgr;
 675	private JEditTextArea textArea;
 676	private BufferChangeHandler bufferChangeHandler;
 677
 678	/**
 679	 * The fold visibility map.
 680	 *
 681	 * All lines from fvm[2*n] to fvm[2*n+1]-1 inclusive are visible.
 682	 * All lines from position fvm[2*n+1] to fvm[2*n+2]-1 inclusive are
 683	 * invisible.
 684	 *
 685	 * Examples:
 686	 * ---------
 687	 * All lines visible: { 0, buffer.getLineCount() }
 688	 * Narrow from a to b: { a, b + 1 }
 689	 * Collapsed fold from a to b: { 0, a + 1, b, buffer.getLineCount() }
 690	 *
 691	 * Note: length is always even.
 692	 */
 693	private int[] fvm;
 694	private int fvmcount;
 695
 696	private int lastfvmget = -1;
 697
 698	//{{{ DisplayManager constructor
 699	private DisplayManager(Buffer buffer, JEditTextArea textArea)
 700	{
 701		this.buffer = buffer;
 702		this.lineMgr = buffer._getLineManager();
 703		this.textArea = textArea;
 704
 705		scrollLineCount = new ScrollLineCount();
 706		firstLine = new FirstLine();
 707
 708		bufferChangeHandler = new BufferChangeHandler();
 709		// this listener priority thing is a bad hack...
 710		buffer.addBufferChangeListener(bufferChangeHandler,
 711			Buffer.HIGH_PRIORITY);
 712	} //}}}
 713
 714	//{{{ dispose() method
 715	private void dispose()
 716	{
 717		buffer.removeBufferChangeListener(bufferChangeHandler);
 718	} //}}}
 719
 720	//{{{ fvmreset() method
 721	private void fvmreset()
 722	{
 723		lastfvmget = -1;
 724		fvmcount = 2;
 725		fvm[0] = 0;
 726		fvm[1] = buffer.getLineCount();
 727	} //}}}
 728
 729	//{{{ fvmget() method
 730	/**
 731	 * Returns the fold visibility map index for the given line.
 732	 */
 733	private int fvmget(int line)
 734	{
 735		scanCount++;
 736
 737		if(line < fvm[0])
 738			return -1;
 739		if(line >= fvm[fvmcount - 1])
 740			return fvmcount - 1;
 741
 742		if(lastfvmget != -1)
 743		{
 744			if(line >= fvm[lastfvmget])
 745			{
 746				if(lastfvmget == fvmcount - 1
 747					|| line < fvm[lastfvmget + 1])
 748				{
 749					return lastfvmget;
 750				}
 751			}
 752		}
 753
 754		int start = 0;
 755		int end = fvmcount - 1;
 756
 757loop:		for(;;)
 758		{
 759			scannedLines++;
 760			switch(end - start)
 761			{
 762			case 0:
 763				lastfvmget = start;
 764				break loop;
 765			case 1:
 766				int value = fvm[end];
 767				if(value <= line)
 768					lastfvmget = end;
 769				else
 770					lastfvmget = start;
 771				break loop;
 772			default:
 773				int pivot = (end + start) / 2;
 774				value = fvm[pivot];
 775				if(value == line)
 776				{
 777					lastfvmget = pivot;
 778					break loop;
 779				}
 780				else if(value < line)
 781					start = pivot;
 782				else
 783					end = pivot - 1;
 784				break;
 785			}
 786		}
 787
 788		return lastfvmget;
 789	} //}}}
 790
 791	//{{{ fvmput() method
 792	/**
 793	 * Replaces from <code>start</code> to <code>end-1</code> inclusive with
 794	 * <code>put</code>. Update <code>fvmcount</code>.
 795	 */
 796	private void fvmput(int start, int end, int[] put)
 797	{
 798		if(Debug.FOLD_VIS_DEBUG)
 799		{
 800			StringBuffer buf = new StringBuffer("{");
 801			if(put != null)
 802			{
 803				for(int i = 0; i < put.length; i++)
 804				{
 805					if(i != 0)
 806						buf.append(',');
 807					buf.append(put[i]);
 808				}
 809			}
 810			buf.append("}");
 811			Log.log(Log.DEBUG,this,"fvmput(" + start + ","
 812				+ end + "," + buf + ")");
 813		}
 814		int putl = (put == null ? 0 : put.length);
 815
 816		int delta = putl - (end - start);
 817		if(fvmcount + delta > fvm.length)
 818		{
 819			int[] newfvm = new int[fvm.length * 2 + 1];
 820			System.arraycopy(fvm,0,newfvm,0,fvmcount);
 821			fvm = newfvm;
 822		}
 823
 824		if(delta != 0)
 825		{
 826			System.arraycopy(fvm,end,fvm,start + putl,
 827				fvmcount - end);
 828		}
 829
 830		if(putl != 0)
 831		{
 832			System.arraycopy(put,0,fvm,start,put.length);
 833		}
 834
 835		fvmcount += delta;
 836
 837		fvmdump();
 838
 839		if(fvmcount == 0)
 840			throw new InternalError();
 841	} //}}}
 842
 843	//{{{ fvmput2() method
 844	/**
 845	 * Merge previous and next entry if necessary.
 846	 */
 847	private void fvmput2(int starti, int endi, int start, int end)
 848	{
 849		if(Debug.FOLD_VIS_DEBUG)
 850		{
 851			Log.log(Log.DEBUG,this,"*fvmput2(" + starti + ","
 852				+ endi + "," + start + "," + end + ")");
 853		}
 854		if(starti != -1 && fvm[starti] == start)
 855		{
 856			if(endi <= fvmcount - 2 && fvm[endi + 1]
 857				== end + 1)
 858			{
 859				fvmput(starti,endi + 2,null);
 860			}
 861			else
 862			{
 863				fvmput(starti,endi + 1,
 864					new int[] { end + 1 });
 865			}
 866		}
 867		else
 868		{
 869			if(endi != fvmcount - 1 && fvm[endi + 1]
 870				== end + 1)
 871			{
 872				fvmput(starti + 1,endi + 2,
 873					new int[] { start });
 874			}
 875			else
 876			{
 877				fvmput(starti + 1,endi + 1,
 878					new int[] { start,
 879					end + 1 });
 880			}
 881		}
 882	} //}}}
 883
 884	//{{{ fvmdump() method
 885	private void fvmdump()
 886	{
 887		if(Debug.FOLD_VIS_DEBUG)
 888		{
 889			StringBuffer buf = new StringBuffer("{");
 890			for(int i = 0; i < fvmcount; i++)
 891			{
 892				if(i != 0)
 893					buf.append(',');
 894				buf.append(fvm[i]);
 895			}
 896			buf.append("}");
 897			Log.log(Log.DEBUG,this,"fvm = " + buf);
 898		}
 899	} //}}}
 900
 901	//{{{ showLineRange() method
 902	private void showLineRange(int start, int end)
 903	{
 904		if(Debug.FOLD_VIS_DEBUG)
 905		{
 906			Log.log(Log.DEBUG,this,"showLineRange(" + start
 907				+ "," + end + ")");
 908		}
 909
 910		for(int i = start; i <= end; i++)
 911		{
 912			//XXX
 913			if(!isLineVisible(i))
 914			{
 915				// important: not lineMgr.getScreenLineCount()
 916				int screenLines = getScreenLineCount(i);
 917				if(firstLine.physicalLine >= i)
 918				{
 919					firstLine.scrollLine += screenLines;
 920					firstLine.callChanged = true;
 921				}
 922				scrollLineCount.scrollLine += screenLines;
 923				scrollLineCount.callChanged = true;
 924			}
 925		}
 926
 927		/* update fold visibility map. */
 928		int starti = fvmget(start);
 929		int endi = fvmget(end);
 930
 931		if(starti % 2 == 0)
 932		{
 933			if(endi % 2 == 0)
 934				fvmput(starti + 1,endi + 1,null);
 935			else
 936			{
 937				if(endi != fvmcount - 1
 938					&& fvm[endi + 1] == end + 1)
 939					fvmput(starti + 1,endi + 2,null);
 940				else
 941				{
 942					fvmput(starti + 1,endi,null);
 943					fvm[starti + 1] = end + 1;
 944				}
 945			}
 946		}
 947		else
 948		{
 949			if(endi % 2 == 0)
 950			{
 951				if(starti != -1 && fvm[starti] == start)
 952					fvmput(starti,endi + 1,null);
 953				else
 954				{
 955					fvmput(starti + 1,endi,null);
 956					fvm[starti + 1] = start;
 957				}
 958			}
 959			else
 960				fvmput2(starti,endi,start,end);
 961		}
 962
 963		lastfvmget = -1;
 964	} //}}}
 965
 966	//{{{ hideLineRange() method
 967	private void hideLineRange(int start, int end)
 968	{
 969		if(Debug.FOLD_VIS_DEBUG)
 970		{
 971			Log.log(Log.DEBUG,this,"hideLineRange(" + start
 972				+ "," + end + ")");
 973		}
 974
 975		int i = start;
 976		if(!isLineVisible(i))
 977			i = getNextVisibleLine(i);
 978		while(i != -1 && i <= end)
 979		{
 980			int screenLines = lineMgr.getScreenLineCount(i);
 981			if(i < firstLine.physicalLine)
 982			{
 983				firstLine.scrollLine -= screenLines;
 984				firstLine.skew = 0;
 985				firstLine.callChanged = true;
 986			}
 987
 988			scrollLineCount.scrollLine -= screenLines;
 989			scrollLineCount.callChanged = true;
 990
 991			i = getNextVisibleLine(i);
 992		}
 993
 994		/* update fold visibility map. */
 995		int starti = fvmget(start);
 996		int endi = fvmget(end);
 997
 998		if(starti % 2 == 0)
 999		{
1000			if(endi % 2 == 0)
1001				fvmput2(starti,endi,start,end);
1002			else
1003			{
1004				if(start == fvm[0])
1005					fvmput(starti,endi + 1,null);
1006				else
1007				{
1008					fvmput(starti + 1,endi,null);
1009					fvm[starti + 1] = start;
1010				}
1011			}
1012		}
1013		else
1014		{
1015			if(endi % 2 == 0)
1016			{
1017				if(end + 1 == fvm[fvmcount - 1])
1018					fvmput(starti + 1,endi + 2,null);
1019				else
1020				{
1021					fvmput(starti + 1,endi,null);
1022					fvm[starti + 1] = end + 1;
1023				}
1024			}
1025			else
1026				fvmput(starti + 1,endi + 1,null);
1027		}
1028
1029		lastfvmget = -1;
1030
1031		if(!isLineVisible(firstLine.physicalLine))
1032		{
1033			int firstVisible = getFirstVisibleLine();
1034			if(firstLine.physicalLine < firstVisible)
1035			{
1036				firstLine.physicalLine = firstVisible;
1037				firstLine.scrollLine = 0;
1038			}
1039			else
1040			{
1041				firstLine.physicalLine = getPrevVisibleLine(
1042					firstLine.physicalLine);
1043				firstLine.scrollLine -=
1044					lineMgr.getScreenLineCount(
1045					firstLine.physicalLine);
1046			}
1047			firstLine.callChanged = true;
1048		}
1049	} //}}}
1050
1051	//{{{ _setScreenLineCount() method
1052	private void _setScreenLineCount(int line, int oldCount, int count)
1053	{
1054		if(!isLineVisible(line))
1055			return;
1056
1057		if(firstLine.physicalLine >= line)
1058		{
1059			if(firstLine.physicalLine == line)
1060				firstLine.callChanged = true;
1061			else
1062			{
1063				firstLine.scrollLine += (count - oldCount);
1064				firstLine.callChanged = true;
1065			}
1066		}
1067
1068		scrollLineCount.scrollLine += (count - oldCount);
1069		scrollLineCount.callChanged = true;
1070	} //}}}
1071
1072	//}}}
1073
1074	//{{{ Anchor class
1075	static abstract class Anchor
1076	{
1077		int physicalLine;
1078		int scrollLine;
1079		boolean callChanged;
1080		boolean callReset;
1081
1082		abstract void reset();
1083		abstract void changed();
1084
1085		public String toString()
1086		{
1087			return getClass().getName() + "[" + physicalLine + ","
1088				+ scrollLine + "]";
1089		}
1090	} //}}}
1091
1092	//{{{ ScrollLineCount class
1093	class ScrollLineCount extends Anchor
1094	{
1095		//{{{ changed() method
1096		public void changed()
1097		{
1098			if(Debug.SCROLL_DEBUG)
1099				Log.log(Log.DEBUG,this,"changed()");
1100			textArea.updateScrollBars();
1101			textArea.recalculateLastPhysicalLine();
1102		} //}}}
1103
1104		//{{{ reset() method
1105		public void reset()
1106		{
1107			if(Debug.SCROLL_DEBUG)
1108				Log.log(Log.DEBUG,this,"reset()");
1109
1110			physicalLine = getFirstVisibleLine();
1111			scrollLine = 0;
1112			while(physicalLine != -1)
1113			{
1114				scrollLine += getScreenLineCount(physicalLine);
1115				physicalLine = getNextVisibleLine(physicalLine);
1116			}
1117
1118			physicalLine = buffer.getLineCount();
1119
1120			firstLine.ensurePhysicalLineIsVisible();
1121
1122			textArea.recalculateLastPhysicalLine();
1123			textArea.updateScrollBars();
1124		} //}}}
1125	} //}}}
1126
1127	//{{{ FirstLine class
1128	class FirstLine extends Anchor
1129	{
1130		int skew;
1131
1132		//{{{ changed() method
1133		public void changed()
1134		{
1135			//{{{ Debug code
1136			if(Debug.SCROLL_DEBUG)
1137			{
1138				Log.log(Log.DEBUG,this,"changed() before: "
1139					+ physicalLine + ":" + scrollLine);
1140			} //}}}
1141
1142			ensurePhysicalLineIsVisible();
1143
1144			int screenLines = getScreenLineCount(physicalLine);
1145			if(skew >= screenLines)
1146				skew = screenLines - 1;
1147
1148			//{{{ Debug code
1149			if(Debug.SCROLL_VERIFY)
1150			{
1151				int verifyScrollLine = 0;
1152
1153				for(int i = 0; i < buffer.getLineCount(); i++)
1154				{
1155					if(!isLineVisible(i))
1156						continue;
1157
1158					if(i >= physicalLine)
1159						break;
1160
1161					verifyScrollLine += getScreenLineCount(i);
1162				}
1163
1164				if(verifyScrollLine != scrollLine)
1165				{
1166					Exception ex = new Exception(scrollLine + ":" + verifyScrollLine);
1167					Log.log(Log.ERROR,this,ex);
1168					new org.gjt.sp.jedit.gui.BeanShellErrorDialog(null,ex);
1169				}
1170			}
1171
1172			if(Debug.SCROLL_DEBUG)
1173			{
1174				Log.log(Log.DEBUG,this,"changed() after: "
1175					+ physicalLine + ":" + scrollLine);
1176			} //}}}
1177
1178			if(!scrollLineCount.callChanged
1179				&& !scrollLineCount.callReset)
1180			{
1181				textArea.updateScrollBars();
1182				textArea.recalculateLastPhysicalLine();
1183			}
1184			else
1185			{
1186				// ScrollLineCount.changed() does the same
1187				// thing
1188			}
1189		} //}}}
1190
1191		//{{{ reset() method
1192		public void reset()
1193		{
1194			if(Debug.SCROLL_DEBUG)
1195				Log.log(Log.DEBUG,this,"reset()");
1196
1197			String wrap = buffer.getStringProperty("wrap");
1198			softWrap = wrap.equals("soft");
1199			if(textArea.maxLineLen <= 0)
1200			{
1201				softWrap = false;
1202				wrapMargin = 0;
1203			}
1204			else
1205			{
1206				// stupidity
1207				char[] foo = new char[textArea.maxLineLen];
1208				for(int i = 0; i < foo.length; i++)
1209				{
1210					foo[i] = ' ';
1211				}
1212				TextAreaPainter painter = textArea.getPainter();
1213				wrapMargin = (int)painter.getFont().getStringBounds(
1214					foo,0,foo.length,
1215					painter.getFontRenderContext())
1216					.getWidth();
1217			}
1218
1219			scrollLine = 0;
1220
1221			int i = getFirstVisibleLine();
1222
1223			for(;;)
1224			{
1225				if(i >= physicalLine)
1226					break;
1227
1228				scrollLine += getScreenLineCount(i);
1229
1230				int nextLine = getNextVisibleLine(i);
1231				if(nextLine == -1)
1232					break;
1233				else
1234					i = nextLine;
1235			}
1236
1237			physicalLine = i;
1238
1239			int screenLines = getScreenLineCount(physicalLine);
1240			if(skew >= screenLines)
1241				skew = screenLines - 1;
1242
1243			textArea.updateScrollBars();
1244		} //}}}
1245
1246		//{{{ physDown() method
1247		// scroll down by physical line amount
1248		void physDown(int amount, int screenAmount)
1249		{
1250			if(Debug.SCROLL_DEBUG)
1251			{
1252				Log.log(Log.DEBUG,this,"physDown() start: "
1253					+ physicalLine + ":" + scrollLine);
1254			}
1255
1256			skew = 0;
1257
1258			if(!isLineVisible(physicalLine))
1259			{
1260				int lastVisibleLine = getLastVisibleLine();
1261				if(physicalLine > lastVisibleLine)
1262					physicalLine = lastVisibleLine;
1263				else
1264				{
1265					int nextPhysicalLine = getNextVisibleLine(physicalLine);
1266					amount -= (nextPhysicalLine - physicalLine);
1267					scrollLine += getScreenLineCount(physicalLine);
1268					physicalLine = nextPhysicalLine;
1269				}
1270			}
1271
1272			for(;;)
1273			{
1274				int nextPhysicalLine = getNextVisibleLine(
1275					physicalLine);
1276				if(nextPhysicalLine == -1)
1277					break;
1278				else if(nextPhysicalLine > physicalLine + amount)
1279					break;
1280				else
1281				{
1282					scrollLine += getScreenLineCount(physicalLine);
1283					amount -= (nextPhysicalLine - physicalLine);
1284					physicalLine = nextPhysicalLine;
1285				}
1286			}
1287
1288			if(Debug.SCROLL_DEBUG)
1289			{
1290				Log.log(Log.DEBUG,this,"physDown() end: "
1291					+ physicalLine + ":" + scrollLine);
1292			}
1293
1294			callChanged = true;
1295
1296			// JEditTextArea.scrollTo() needs this to simplify
1297			// its code
1298			if(screenAmount < 0)
1299				scrollUp(-screenAmount);
1300			else if(screenAmount > 0)
1301				scrollDown(screenAmount);
1302		} //}}}
1303
1304		//{{{ physUp() method
1305		// scroll up by physical line amount
1306		void physUp(int amount, int screenAmount)
1307		{
1308			if(Debug.SCROLL_DEBUG)
1309			{
1310				Log.log(Log.DEBUG,this,"physUp() start: "
1311					+ physicalLine + ":" + scrollLine);
1312			}
1313
1314			skew = 0;
1315
1316			if(!isLineVisible(physicalLine))
1317			{
1318				int firstVisibleLine = getFirstVisibleLine();
1319				if(physicalLine < firstVisibleLine)
1320					physicalLine = firstVisibleLine;
1321				else
1322				{
1323					int prevPhysicalLine = getPrevVisibleLine(physicalLine);
1324					amount -= (physicalLine - prevPhysicalLine);
1325				}
1326			}
1327
1328			for(;;)
1329			{
1330				int prevPhysicalLine = getPrevVisibleLine(
1331					physicalLine);
1332				if(prevPhysicalLine == -1)
1333					break;
1334				else if(prevPhysicalLine < physicalLine - amount)
1335					break;
1336				else
1337				{
1338					amount -= (physicalLine - prevPhysicalLine);
1339					physicalLine = prevPhysicalLine;
1340					scrollLine -= getScreenLineCount(
1341						prevPhysicalLine);
1342				}
1343			}
1344
1345			if(Debug.SCROLL_DEBUG)
1346			{
1347				Log.log(Log.DEBUG,this,"physUp() end: "
1348					+ physicalLine + ":" + scrollLine);
1349			}
1350
1351			callChanged = true;
1352
1353			// JEditTextArea.scrollTo() needs this to simplify
1354			// its code
1355			if(screenAmount < 0)
1356				scrollUp(-screenAmount);
1357			else if(screenAmount > 0)
1358				scrollDown(screenAmount);
1359		} //}}}
1360
1361		//{{{ scrollDown() method
1362		// scroll down by screen line amount
1363		void scrollDown(int amount)
1364		{
1365			if(Debug.SCROLL_DEBUG)
1366				Log.log(Log.DEBUG,this,"scrollDown()");
1367
1368			ensurePhysicalLineIsVisible();
1369
1370			amount += skew;
1371
1372			skew = 0;
1373
1374			while(amount > 0)
1375			{
1376				int screenLines = getScreenLineCount(physicalLine);
1377				if(amount < screenLines)
1378				{
1379					skew = amount;
1380					break;
1381				}
1382				else
1383				{
1384					int nextLine = getNextVisibleLine(physicalLine);
1385					if(nextLine == -1)
1386						break;
1387					boolean visible = isLineVisible(physicalLine);
1388					physicalLine = nextLine;
1389					if(visible)
1390					{
1391						amount -= screenLines;
1392						scrollLine += screenLines;
1393					}
1394				}
1395			}
1396
1397			callChanged = true;
1398		} //}}}
1399
1400		//{{{ scrollUp() method
1401		// scroll up by screen line amount
1402		void scrollUp(int amount)
1403		{
1404			if(Debug.SCROLL_DEBUG)
1405				Log.log(Log.DEBUG,this,"scrollUp()");
1406
1407			ensurePhysicalLineIsVisible();
1408
1409			if(amount <= skew)
1410			{
1411				skew -= amount;
1412			}
1413			else
1414			{
1415				amount -= skew;
1416				skew = 0;
1417
1418				while(amount > 0)
1419				{
1420					int prevLine = getPrevVisibleLine(physicalLine);
1421					if(prevLine == -1)
1422						break;
1423					physicalLine = prevLine;
1424
1425					int screenLines = getScreenLineCount(physicalLine);
1426					scrollLine -= screenLines;
1427					if(amount < screenLines)
1428					{
1429						skew = screenLines - amount;
1430						break;
1431					}
1432					else
1433						amount -= screenLines;
1434				}
1435			}
1436
1437			callChanged = true;
1438		} //}}}
1439
1440		//{{{ ensurePhysicalLineIsVisible() method
1441		private void ensurePhysicalLineIsVisible()
1442		{
1443			if(!isLineVisible(physicalLine))
1444			{
1445				if(physicalLine > getLastVisibleLine())
1446				{
1447					physicalLine = getLastVisibleLine();
1448					scrollLine = getScrollLineCount() - 1;
1449				}
1450				else if(physicalLine < getFirstVisibleLine())
1451				{
1452					physicalLine = getFirstVisibleLine();
1453					scrollLine = 0;
1454				}
1455				else
1456				{
1457					physicalLine = getNextVisibleLine(physicalLine);
1458					scrollLine += getScreenLineCount(physicalLine);
1459				}
1460			}
1461		} //}}}
1462	} //}}}
1463
1464	//{{{ BufferChangeHandler class
1465	/**
1466	 * Note that in this class we take great care to defer complicated
1467	 * calculations to the end of the current transaction if the buffer
1468	 * informs us a compound edit is in progress
1469	 * (<code>isTransactionInProgress()</code>).
1470	 *
1471	 * This greatly speeds up replace all for example, by only doing certain
1472	 * things once, particularly in <code>moveCaretPosition()</code>.
1473	 *
1474	 * Try doing a replace all in a large file, for example. It is very slow
1475	 * in 3.2, faster in 4.0 (where the transaction optimization was
1476	 * introduced) and faster still in 4.1 (where it was further improved).
1477	 *
1478	 * There is still work to do; see TODO.txt.
1479	 */
1480	class BufferChangeHandler extends BufferChangeAdapter
1481	{
1482		boolean delayedUpdate;
1483		boolean delayedMultilineUpdate;
1484		int delayedUpdateStart;
1485		int delayedUpdateEnd;
1486
1487		//{{{ foldHandlerChanged() method
1488		public void foldHandlerChanged(Buffer buffer)
1489		{
1490			fvmreset();
1491
1492			firstLine.callReset = true;
1493			scrollLineCount.callReset = true;
1494
1495			int collapseFolds = buffer.getIntegerProperty(
1496				"collapseFolds",0);
1497			if(collapseFolds != 0)
1498				expandFolds(collapseFolds);
1499
1500			_notifyScreenLineChanges();
1501		} //}}}
1502
1503		//{{{ foldLevelChanged() method
1504		public void foldLevelChanged(Buffer buffer, int start, int end)
1505		{
1506			System.err.println("Invalidate " + (start-1) + " to " + textArea.getLastPhysicalLine() + "," + end);
1507
1508			if(textArea.getDisplayManager() == DisplayManager.this
1509				&& end != 0 && buffer.isLoaded())
1510			{
1511				textArea.invalidateLineRange(start - 1,
1512					textArea.getLastPhysicalLine());
1513			}
1514		} //}}}
1515
1516		//{{{ contentInserted() method
1517		public void contentInserted(Buffer buffer, int startLine,
1518			int offset, int numLines, int length)
1519		{
1520			if(!buffer.isLoaded())
1521			{
1522				fvmreset();
1523				return;
1524			}
1525
1526			int endLine = startLine + numLines;
1527
1528			if(numLines != 0)
1529			{
1530				delayedMultilineUpdate = true;
1531
1532				/* this is a sloppy hack to fix bug
1533				   "[ 677902 ] hitting return after collapsed
1534				   fold"
1535
1536				   the idea is that if we extend the range then
1537				   the problem described in the bug happends, so
1538				   if the insert is at the very end of the range
1539				   we don't extend it, instead we push the
1540				   insert into the next range, however for this
1541				   to work properly we also have to mess with
1542				   screen line counts. */
1543				int index = fvmget(startLine);
1544				int start = index + 1;
1545				/* if(start + 1 < fvmcount && fvm[start]
1546					== startLine + 1)
1547				{
1548					if(index % 2 == 0)
1549					{
1550						System.err.println("case 1");
1551						scrollLineCount.scrollLine -=
1552							getScreenLineCount(
1553							startLine + 1);
1554						start++;
1555					}
1556				} */
1557
1558				for(int i = start; i < fvmcount; i++)
1559				{
1560					fvm[i] += numLines;
1561				}
1562
1563				lastfvmget = -1;
1564				fvmdump();
1565
1566			}
1567
1568			if(textArea.getDisplayManager() == DisplayManager.this)
1569			{
1570				if(numLines != 0)
1571				{
1572					contentInserted(firstLine,startLine,numLines);
1573					contentInserted(scrollLineCount,startLine,numLines);
1574				}
1575
1576				if(delayedUpdateEnd >= startLine)
1577					delayedUpdateEnd += numLines;
1578				delayedUpdate(startLine,endLine);
1579
1580				//{{{ resize selections if necessary
1581				for(int i = 0; i < textArea.selection.size(); i++)
1582				{
1583					Selection s = (Selection)textArea
1584						.selection.elementAt(i);
1585	
1586					if(s.contentInserted(buffer,startLine,offset,
1587						numLines,length))
1588					{
1589						delayedUpdate(s.startLine,s.endLine);
1590					}
1591				} //}}}
1592
1593				int caret = textArea.getCaretPosition();
1594				if(caret >= offset)
1595				{
1596					int scrollMode = (caretAutoScroll()
1597						? JEditTextArea.ELECTRIC_SCROLL
1598						: JEditTextArea.NO_SCROLL);
1599					textArea.moveCaretPosition(
1600						caret + length,scrollMode);
1601				}
1602				else
1603				{
1604					int scrollMode = (caretAutoScroll()
1605						? JEditTextArea.NORMAL_SCROLL
1606						: JEditTextArea.NO_SCROLL);
1607					textArea.moveCaretPosition(
1608						caret,scrollMode);
1609				}
1610			}
1611			else
1612			{
1613				firstLine.callReset = true;
1614				scrollLineCount.callReset = true;
1615			}
1616		} //}}}
1617
1618		//{{{ preContentRemoved() method
1619		public void preContentRemoved(Buffer buffer, int startLine,
1620			int offset, int numLines, int length)
1621		{
1622			if(!buffer.isLoaded())
1623				return;
1624
1625			if(textArea.getDisplayManager() == DisplayManager.this)
1626			{
1627				if(numLines != 0)
1628				{
1629					preContentRemoved(firstLine,startLine,numLines);
1630					preContentRemoved(scrollLineCount,startLine,numLines);
1631				}
1632
1633				if(delayedUpdateEnd >= startLine)
1634					delayedUpdateEnd -= numLines;
1635				delayedUpdate(startLine,startLine);
1636			}
1637			else
1638			{
1639				firstLine.callReset = true;
1640				scrollLineCount.callReset = true;
1641			}
1642
1643			if(numLines != 0)
1644			{
1645				delayedMultilineUpdate = true;
1646
1647				int endLine = startLine + numLines;
1648
1649				/* update fold visibility map. */
1650				int starti = fvmget(startLine);
1651				int endi = fvmget(endLine);
1652
1653				/* both have same visibility; just remove
1654				 * anything in between. */
1655				if(Math.abs(starti % 2) == Math.abs(endi % 2))
1656				{
1657					if(endi - starti == fvmcount)
1658					{
1659						// we're removing from before
1660						// the first visible to after
1661						// the last visible
1662						fvmreset();
1663						firstLine.callReset = true;
1664						scrollLineCount.callReset = true;
1665					}
1666					else
1667					{
1668						fvmput(starti + 1,endi + 1,null);
1669						starti++;
1670					}
1671				}
1672				/* collapse 2 */
1673				else if(starti != -1 && fvm[starti] == startLine)
1674				{
1675					//int newStart = fvm[endi + 1] - 1;
1676					fvmput(starti,endi + 1,null);
1677					//fvm[starti] = newStart;
1678					//starti++;
1679				}
1680				/* shift */
1681				else
1682				{
1683					fvmput(starti + 1,endi,null);
1684					fvm[starti + 1] = startLine;
1685					starti += 2;
1686				}
1687
1688				/* update */
1689				for(int i = starti; i < fvmcount; i++)
1690					fvm[i] -= numLines;
1691
1692				if(firstLine.physicalLine
1693					> getLastVisibleLine()
1694					|| firstLine.physicalLine
1695					< getFirstVisibleLine())
1696				{
1697					// will be handled later.
1698					// see comments at the end of
1699					// transactionComplete().
1700				}
1701				// very subtle... if we leave this for
1702				// ensurePhysicalLineIsVisible(), an
1703				// extra line will be added to the
1704				// scroll line count.
1705				else if(!isLineVisible(
1706					firstLine.physicalLine))
1707				{
1708					firstLine.physicalLine =
1709						getNextVisibleLine(
1710						firstLine.physicalLine);
1711				}
1712
1713				lastfvmget = -1;
1714				fvmdump();
1715			}
1716		} //}}}
1717
1718		//{{{ contentRemoved() method
1719		public void contentRemoved(Buffer buffer, int startLine,
1720			int start, int numLines, int length)
1721		{
1722			if(!buffer.isLoaded())
1723				return;
1724
1725			if(textArea.getDisplayManager() == DisplayManager.this)
1726			{
1727				int endLine = startLine + numLines;
1728
1729				//{{{ resize selections if necessary
1730				for(int i = 0; i < textArea.selection.size(); i++)
1731				{
1732					Selection s = (Selection)textArea
1733						.selection.elementAt(i);
1734	
1735					if(s.contentRemoved(buffer,startLine,
1736						start,numLines,length))
1737					{
1738						delayedUpdate(s.startLine,s.endLine);
1739						if(s.start == s.end)
1740						{
1741							textArea.selection.removeElementAt(i);
1742							i--;
1743						}
1744					}
1745				} //}}}
1746
1747				int caret = textArea.getCaretPosition();
1748
1749				if(caret >= start + length)
1750				{
1751					int scrollMode = (caretAutoScroll()
1752						? JEditTextArea.ELECTRIC_SCROLL
1753						: JEditTextArea.NO_SCROLL);
1754					textArea.moveCaretPosition(
1755						caret - length,
1756						scrollMode);
1757				}
1758				else if(caret >= start)
1759				{
1760					int scrollMode = (caretAutoScroll()
1761						? JEditTextArea.ELECTRIC_SCROLL
1762						: JEditTextArea.NO_SCROLL);
1763					textArea.moveCaretPosition(
1764						start,scrollMode);
1765				}
1766				else
1767				{
1768					int scrollMode = (caretAutoScroll()
1769						? JEditTextArea.NORMAL_SCROLL
1770						: JEditTextArea.NO_SCROLL);
1771					textArea.moveCaretPosition(caret,scrollMode);
1772				}
1773			}
1774		}
1775		//}}}
1776
1777		//{{{ transactionComplete() method
1778		public void transactionComplete(Buffer buffer)
1779		{
1780			if(textArea.getDisplayManager() == DisplayManager.this)
1781			{
1782				if(delayedUpdate)
1783				{
1784					// must be before the below call
1785					// so that the chunk cache is not
1786					// updated with an invisible first
1787					// line (see above)
1788					_notifyScreenLineChanges();
1789
1790					if(delayedMultilineUpdate)
1791					{
1792						textArea.invalidateScreenLineRange(
1793							textArea.chunkCache
1794							.getScreenLineOfOffset(
1795							delayedUpdateStart,0),
1796							textArea.getVisibleLines());
1797						delayedMultilineUpdate = false;
1798					}
1799					else
1800					{
1801						textArea.invalidateLineRange(
1802							delayedUpdateStart,
1803							delayedUpdateEnd);
1804					}
1805
1806					int _firstLine = textArea.getFirstPhysicalLine();
1807					int _lastLine = textArea.getLastPhysicalLine();
1808
1809					int line = delayedUpdateStart;
1810					if(!isLineVisible(line))
1811						line = getNextVisibleLine(line);
1812					while(line != -1 && line <= delayedUpdateEnd)
1813					{
1814						if(line < _firstLine
1815							|| line > _lastLine)
1816						{
1817							getScreenLineCount(line);
1818						}
1819						line = getNextVisibleLine(line);
1820					}
1821
1822					// update visible lines
1823					int visibleLines = textArea
1824						.getVisibleLines();
1825					if(visibleLines != 0)
1826					{
1827						textArea.chunkCache.getLineInfo(
1828							visibleLines - 1);
1829					}
1830
1831					// force the fold levels to be
1832					// updated.
1833
1834					// when painting the last line of
1835					// a buffer, Buffer.isFoldStart()
1836					// doesn't call getFoldLevel(),
1837					// hence the foldLevelChanged()
1838					// event might not be sent for the
1839					// previous line.
1840
1841					buffer.getFoldLevel(delayedUpdateEnd);
1842				}
1843
1844				textArea._finishCaretUpdate();
1845			}
1846
1847			//{{{ Debug code
1848			if(Debug.SCROLL_VERIFY)
1849			{
1850				int scrollLineCount = 0;
1851				for(int i = 0; i < textArea.getLineCount(); i++)
1852				{
1853					if(isLineVisible(i))
1854					{
1855						scrollLineCount +=
1856							getScreenLineCount(i);
1857					}
1858				}
1859				if(scrollLineCount != getScrollLineCount())
1860				{
1861					throw new InternalError(scrollLineCount
1862						+ " != "
1863						+ getScrollLineCount());
1864				}
1865			} //}}}
1866
1867			delayedUpdate = false;
1868		} //}}}
1869
1870		//{{{ contentInserted() method
1871		private void contentInserted(Anchor anchor, int startLine,
1872			int numLines)
1873		{
1874			if(anchor.physicalLine >= startLine)
1875			{
1876				if(anchor.physicalLine != startLine)
1877					anchor.physicalLine += numLines;
1878				anchor.callChanged = true;
1879			}
1880		} //}}}
1881
1882		//{{{ preContentRemoved() method
1883		private void preContentRemoved(Anchor anchor, int startLine,
1884			int numLines)
1885		{
1886			if(anchor.physicalLine >= startLine)
1887			{
1888				if(anchor.physicalLine == startLine)
1889					anchor.callChanged = true;
1890				else
1891				{
1892					int end = Math.min(startLine + numLines,
1893						anchor.physicalLine);
1894					for(int i = startLine; i < end; i++)
1895					{
1896						//XXX
1897						if(isLineVisible(i))
1898						{
1899							anchor.scrollLine -=
1900								lineMgr
1901								.getScreenLineCount(i);
1902						}
1903					}
1904					anchor.physicalLine -= (end - startLine);
1905					anchor.callChanged = true;
1906				}
1907			}
1908		} //}}}
1909
1910		//{{{ delayedUpdate() method
1911		private void delayedUpdate(int startLine, int endLine)
1912		{
1913			textArea.chunkCache.invalidateChunksFromPhys(startLine);
1914			if(!delayedUpdate)
1915			{
1916				delayedUpdateStart = startLine;
1917				delayedUpdateEnd = endLine;
1918				delayedUpdate = true;
1919			}
1920			else
1921			{
1922				delayedUpdateStart = Math.min(
1923					delayedUpdateStart,
1924					startLine);
1925				delayedUpdateEnd = Math.max(
1926					delayedUpdateEnd,
1927					endLine);
1928			}
1929		} //}}}
1930
1931		//{{{ caretAutoScroll() method
1932		/**
1933		 * Return if change in buffer should scroll this text area.
1934		 */
1935		private boolean caretAutoScroll()
1936		{
1937			View view = textArea.getView();
1938			return view == jEdit.getActiveView()
1939				&& view.getTextArea() == textArea;
1940		} //}}}
1941	} //}}}
1942}