PageRenderTime 80ms CodeModel.GetById 19ms app.highlight 50ms RepoModel.GetById 1ms app.codeStats 1ms

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

#
Java | 1614 lines | 1168 code | 190 blank | 256 comment | 275 complexity | a1a065b05c09ea9e60f0406096999142 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, 2003 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 4803 2003-06-21 00:28:50Z 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	//{{{ _setScreenLineCount() method
 110	private static void _setScreenLineCount(Buffer buffer, int line,
 111		int oldCount, int count)
 112	{
 113		Iterator iter = ((List)bufferMap.get(buffer)).iterator();
 114		while(iter.hasNext())
 115		{
 116			((DisplayManager)iter.next())._setScreenLineCount(line,oldCount,count);
 117		}
 118	} //}}}
 119
 120	//{{{ _notifyScreenLineChanges() method
 121	/* static void _notifyScreenLineChanges(Buffer buffer)
 122	{
 123		Iterator iter = ((List)bufferMap.get(buffer)).iterator();
 124		while(iter.hasNext())
 125		{
 126			((DisplayManager)iter.next())._notifyScreenLineChanges();
 127		}
 128	} */ //}}}
 129
 130	private static Map bufferMap = new HashMap();
 131	//}}}
 132
 133	//{{{ isLineVisible() method
 134	/**
 135	 * Returns if the specified line is visible.
 136	 * @param line A physical line index
 137	 * @since jEdit 4.2pre1
 138	 */
 139	public final boolean isLineVisible(int line)
 140	{
 141		return fvmget(line) % 2 == 0;
 142	} //}}}
 143
 144	//{{{ getFirstVisibleLine() method
 145	/**
 146	 * Returns the physical line number of the first visible line.
 147	 * @since jEdit 4.2pre1
 148	 */
 149	public int getFirstVisibleLine()
 150	{
 151		return fvm[0];
 152	} //}}}
 153
 154	//{{{ getLastVisibleLine() method
 155	/**
 156	 * Returns the physical line number of the last visible line.
 157	 * @since jEdit 4.2pre1
 158	 */
 159	public int getLastVisibleLine()
 160	{
 161		return fvm[fvmcount - 1] - 1;
 162	} //}}}
 163
 164	//{{{ getNextVisibleLine() method
 165	/**
 166	 * Returns the next visible line after the specified line index.
 167	 * @param line A physical line index
 168	 * @since jEdit 4.0pre1
 169	 */
 170	public int getNextVisibleLine(int line)
 171	{
 172		int index = fvmget(line);
 173		/* in collapsed range */
 174		if(index % 2 != 0)
 175		{
 176			/* beyond last visible line */
 177			if(fvmcount == index + 1)
 178				return - 1;
 179			/* start of next expanded range */
 180			else
 181				return fvm[index + 1];
 182		}
 183		/* last in expanded range */
 184		else if(line == fvm[index + 1] - 1)
 185		{
 186			/* equal to last visible line */
 187			if(fvmcount == index + 2)
 188				return -1;
 189			/* start of next expanded range */
 190			else
 191				return fvm[index + 2];
 192		}
 193		/* next in expanded range */
 194		else
 195			return line + 1;
 196	} //}}}
 197
 198	//{{{ getPrevVisibleLine() method
 199	/**
 200	 * Returns the previous visible line before the specified line index.
 201	 * @param line A physical line index
 202	 * @since jEdit 4.0pre1
 203	 */
 204	public int getPrevVisibleLine(int line)
 205	{
 206		int index = fvmget(line);
 207		/* before first visible line */
 208		if(index == -1)
 209			return -1;
 210		/* in collapsed range */
 211		else if(index % 2 == 1)
 212		{
 213			/* end of prev expanded range */
 214			return fvm[index] - 1;
 215		}
 216		/* first in expanded range */
 217		else if(line == fvm[index])
 218		{
 219			/* equal to first visible line */
 220			if(index == 0)
 221				return -1;
 222			/* end of prev expanded range */
 223			else
 224				return fvm[index - 1] - 1;
 225		}
 226		/* prev in expanded range */
 227		else
 228			return line - 1;
 229	} //}}}
 230
 231	//{{{ getScreenLineCount() method
 232	public final int getScreenLineCount(int line)
 233	{
 234		if(lineMgr.isScreenLineCountValid(line))
 235			return lineMgr.getScreenLineCount(line);
 236		else
 237		{
 238			int newCount = textArea.chunkCache
 239				.getLineSubregionCount(line);
 240
 241			setScreenLineCount(line,newCount);
 242			return newCount;
 243		}
 244	} //}}}
 245
 246	//{{{ getScrollLineCount() method
 247	public final int getScrollLineCount()
 248	{
 249		return scrollLineCount.scrollLine;
 250	} //}}}
 251
 252	//{{{ collapseFold() method
 253	/**
 254	 * Collapses the fold at the specified physical line index.
 255	 * @param line A physical line index
 256	 * @since jEdit 4.2pre1
 257	 */
 258	public void collapseFold(int line)
 259	{
 260		int lineCount = buffer.getLineCount();
 261		int start = 0;
 262		int end = lineCount - 1;
 263
 264		// if the caret is on a collapsed fold, collapse the
 265		// parent fold
 266		if(line != 0
 267			&& line != buffer.getLineCount() - 1
 268			&& buffer.isFoldStart(line)
 269			&& !isLineVisible(line + 1))
 270		{
 271			line--;
 272		}
 273
 274		int initialFoldLevel = buffer.getFoldLevel(line);
 275
 276		//{{{ Find fold start and end...
 277		if(line != lineCount - 1
 278			&& buffer.getFoldLevel(line + 1) > initialFoldLevel)
 279		{
 280			// this line is the start of a fold
 281			start = line + 1;
 282
 283			for(int i = line + 1; i < lineCount; i++)
 284			{
 285				if(buffer.getFoldLevel(i) <= initialFoldLevel)
 286				{
 287					end = i - 1;
 288					break;
 289				}
 290			}
 291		}
 292		else
 293		{
 294			boolean ok = false;
 295
 296			// scan backwards looking for the start
 297			for(int i = line - 1; i >= 0; i--)
 298			{
 299				if(buffer.getFoldLevel(i) < initialFoldLevel)
 300				{
 301					start = i + 1;
 302					ok = true;
 303					break;
 304				}
 305			}
 306
 307			if(!ok)
 308			{
 309				// no folds in buffer
 310				return;
 311			}
 312
 313			for(int i = line + 1; i < lineCount; i++)
 314			{
 315				if(buffer.getFoldLevel(i) < initialFoldLevel)
 316				{
 317					end = i - 1;
 318					break;
 319				}
 320			}
 321		} //}}}
 322
 323		// Collapse the fold...
 324		hideLineRange(start,end);
 325
 326		_notifyScreenLineChanges();
 327		textArea.foldStructureChanged();
 328	} //}}}
 329
 330	//{{{ expandFold() method
 331	/**
 332	 * Expands the fold at the specified physical line index.
 333	 * @param line A physical line index
 334	 * @param fully If true, all subfolds will also be expanded
 335	 * @since jEdit 4.2pre1
 336	 */
 337	public int expandFold(int line, boolean fully)
 338	{
 339		// the first sub-fold. used by JEditTextArea.expandFold().
 340		int returnValue = -1;
 341
 342		int lineCount = buffer.getLineCount();
 343		int start = 0;
 344		int end = lineCount - 1;
 345
 346		int initialFoldLevel = buffer.getFoldLevel(line);
 347
 348		//{{{ Find fold start and fold end...
 349		if(line != lineCount - 1
 350			&& isLineVisible(line)
 351			&& !isLineVisible(line + 1)
 352			&& buffer.getFoldLevel(line + 1) > initialFoldLevel)
 353		{
 354			// this line is the start of a fold
 355			start = line + 1;
 356
 357			for(int i = line + 1; i < lineCount; i++)
 358			{
 359				if(/* isLineVisible(i) && */
 360					buffer.getFoldLevel(i) <= initialFoldLevel)
 361				{
 362					end = i - 1;
 363					break;
 364				}
 365			}
 366		}
 367		else
 368		{
 369			boolean ok = false;
 370
 371			// scan backwards looking for the start
 372			for(int i = line - 1; i >= 0; i--)
 373			{
 374				//XXX
 375				if(isLineVisible(i) && buffer.getFoldLevel(i) < initialFoldLevel)
 376				{
 377					start = i + 1;
 378					ok = true;
 379					break;
 380				}
 381			}
 382
 383			if(!ok)
 384			{
 385				// no folds in buffer
 386				return -1;
 387			}
 388
 389			for(int i = line + 1; i < lineCount; i++)
 390			{
 391				//XXX
 392				if((isLineVisible(i) &&
 393					buffer.getFoldLevel(i) < initialFoldLevel)
 394					|| i == getLastVisibleLine())
 395				{
 396					end = i - 1;
 397					break;
 398				}
 399			}
 400		} //}}}
 401
 402		//{{{ Expand the fold...
 403		if(fully)
 404		{
 405			showLineRange(start,end);
 406		}
 407		else
 408		{
 409			// we need a different value of initialFoldLevel here!
 410			initialFoldLevel = buffer.getFoldLevel(start);
 411
 412			int firstVisible = start;
 413
 414			for(int i = start; i <= end; i++)
 415			{
 416				if(buffer.getFoldLevel(i) > initialFoldLevel)
 417				{
 418					if(returnValue == -1
 419						&& i != 0
 420						&& buffer.isFoldStart(i - 1))
 421					{
 422						returnValue = i - 1;
 423					}
 424
 425					if(firstVisible != i)
 426					{
 427						showLineRange(firstVisible,i - 1);
 428					}
 429					firstVisible = i + 1;
 430				}
 431			}
 432
 433			if(firstVisible != end + 1)
 434				showLineRange(firstVisible,end);
 435
 436			if(!isLineVisible(line))
 437			{
 438				// this is a hack, and really needs to be done better.
 439				expandFold(line,false);
 440				return returnValue;
 441			}
 442		} //}}}
 443
 444		_notifyScreenLineChanges();
 445		textArea.foldStructureChanged();
 446
 447		return returnValue;
 448	} //}}}
 449
 450	//{{{ expandAllFolds() method
 451	/**
 452	 * Expands all folds.
 453	 * @since jEdit 4.2pre1
 454	 */
 455	public void expandAllFolds()
 456	{
 457		showLineRange(0,buffer.getLineCount() - 1);
 458		_notifyScreenLineChanges();
 459		textArea.foldStructureChanged();
 460	} //}}}
 461
 462	//{{{ expandFolds() method
 463	/**
 464	 * This method should only be called from <code>actions.xml</code>.
 465	 * @since jEdit 4.2pre1
 466	 */
 467	public void expandFolds(char digit)
 468	{
 469		if(digit < '1' || digit > '9')
 470		{
 471			Toolkit.getDefaultToolkit().beep();
 472			return;
 473		}
 474		else
 475			expandFolds((int)(digit - '1') + 1);
 476	} //}}}
 477
 478	//{{{ expandFolds() method
 479	/**
 480	 * Expands all folds with the specified fold level.
 481	 * @param foldLevel The fold level
 482	 * @since jEdit 4.2pre1
 483	 */
 484	public void expandFolds(int foldLevel)
 485	{
 486		if(buffer.getFoldHandler() instanceof IndentFoldHandler)
 487			foldLevel = (foldLevel - 1) * buffer.getIndentSize() + 1;
 488
 489		showLineRange(0,buffer.getLineCount() - 1);
 490
 491		/* this ensures that the first line is always visible */
 492		boolean seenVisibleLine = false;
 493
 494		int firstInvisible = 0;
 495
 496		for(int i = 0; i < buffer.getLineCount(); i++)
 497		{
 498			if(!seenVisibleLine || buffer.getFoldLevel(i) < foldLevel)
 499			{
 500				if(firstInvisible != i)
 501				{
 502					hideLineRange(firstInvisible,
 503						i - 1);
 504				}
 505				firstInvisible = i + 1;
 506				seenVisibleLine = true;
 507			}
 508		}
 509
 510		if(firstInvisible != buffer.getLineCount())
 511			hideLineRange(firstInvisible,buffer.getLineCount() - 1);
 512
 513		_notifyScreenLineChanges();
 514		textArea.foldStructureChanged();
 515	} //}}}
 516
 517	//{{{ narrow() method
 518	/**
 519	 * Narrows the visible portion of the buffer to the specified
 520	 * line range.
 521	 * @param start The first line
 522	 * @param end The last line
 523	 * @since jEdit 4.2pre1
 524	 */
 525	public void narrow(int start, int end)
 526	{
 527		if(start > end || start < 0 || end >= buffer.getLineCount())
 528			throw new ArrayIndexOutOfBoundsException(start + ", " + end);
 529
 530		if(start < getFirstVisibleLine() || end > getLastVisibleLine())
 531			expandAllFolds();
 532
 533		hideLineRange(0,start - 1);
 534		hideLineRange(end + 1,buffer.getLineCount() - 1);
 535
 536		// if we narrowed to a single collapsed fold
 537		if(start != buffer.getLineCount() - 1
 538			&& !isLineVisible(start + 1))
 539			expandFold(start,false);
 540
 541		// Hack... need a more direct way of obtaining a view?
 542		// JEditTextArea.getView() method?
 543		GUIUtilities.getView(textArea).getStatus().setMessageAndClear(
 544			jEdit.getProperty("view.status.narrow"));
 545
 546		_notifyScreenLineChanges();
 547		textArea.foldStructureChanged();
 548	} //}}}
 549
 550	//{{{ Package-private members
 551	boolean softWrap;
 552	int wrapMargin;
 553	FirstLine firstLine;
 554	ScrollLineCount scrollLineCount;
 555
 556	//{{{ init() method
 557	void init()
 558	{
 559		if(!initialized)
 560		{
 561			initialized = true;
 562			fvm = new int[2];
 563			if(buffer.isLoaded())
 564				bufferChangeHandler.foldHandlerChanged(buffer);
 565			else
 566				fvmreset();
 567			_notifyScreenLineChanges();
 568		}
 569		else
 570		{
 571			_notifyScreenLineChanges();
 572			textArea.updateScrollBars();
 573			textArea.recalculateLastPhysicalLine();
 574		}
 575	} //}}}
 576
 577	//{{{ setScreenLineCount() method
 578	/**
 579	 * Sets the number of screen lines that the specified physical line
 580	 * is split into.
 581	 * @since jEdit 4.2pre1
 582	 */
 583	void setScreenLineCount(int line, int count)
 584	{
 585		int oldCount = lineMgr.getScreenLineCount(line);
 586		// still have to call this even if it equals the
 587		// old one so that the offset manager sets the
 588		// validity flag!
 589		lineMgr.setScreenLineCount(line,count);
 590		// this notifies each display manager editing this
 591		// buffer of the screen line count change
 592		if(count != oldCount)
 593			_setScreenLineCount(buffer,line,oldCount,count);
 594	} //}}}
 595
 596	//{{{ updateWrapSettings() method
 597	void updateWrapSettings()
 598	{
 599		String wrap = buffer.getStringProperty("wrap");
 600		softWrap = wrap.equals("soft");
 601		if(textArea.maxLineLen <= 0)
 602		{
 603			softWrap = false;
 604			wrapMargin = 0;
 605		}
 606		else
 607		{
 608			// stupidity
 609			char[] foo = new char[textArea.maxLineLen];
 610			for(int i = 0; i < foo.length; i++)
 611			{
 612				foo[i] = ' ';
 613			}
 614			TextAreaPainter painter = textArea.getPainter();
 615			wrapMargin = (int)painter.getFont().getStringBounds(
 616				foo,0,foo.length,
 617				painter.getFontRenderContext())
 618				.getWidth();
 619		}
 620	} //}}}
 621
 622	//{{{ _notifyScreenLineChanges() method
 623	void _notifyScreenLineChanges()
 624	{
 625		if(Debug.SCROLL_DEBUG)
 626			Log.log(Log.DEBUG,this,"_notifyScreenLineChanges()");
 627
 628		// when the text area switches to us, it will do
 629		// a reset anyway
 630		if(textArea.getDisplayManager() == this)
 631		{
 632			if(firstLine.callReset)
 633				firstLine.reset();
 634			else if(firstLine.callChanged)
 635				firstLine.changed();
 636
 637			if(scrollLineCount.callReset)
 638				scrollLineCount.reset();
 639			else if(scrollLineCount.callChanged)
 640				scrollLineCount.changed();
 641
 642			firstLine.callReset = firstLine.callChanged = false;
 643			scrollLineCount.callReset = scrollLineCount.callChanged = false;
 644		}
 645	} //}}}
 646
 647	//}}}
 648
 649	//{{{ Private members
 650	private boolean initialized;
 651	private boolean inUse;
 652	private Buffer buffer;
 653	private LineManager lineMgr;
 654	private JEditTextArea textArea;
 655	private BufferChangeHandler bufferChangeHandler;
 656
 657	/**
 658	 * The fold visibility map.
 659	 *
 660	 * All lines from fvm[2*n] to fvm[2*n+1]-1 inclusive are visible.
 661	 * All lines from position fvm[2*n+1] to fvm[2*n+2]-1 inclusive are
 662	 * invisible.
 663	 *
 664	 * Examples:
 665	 * ---------
 666	 * All lines visible: { 0, buffer.getLineCount() }
 667	 * Narrow from a to b: { a, b + 1 }
 668	 * Collapsed fold from a to b: { 0, a + 1, b, buffer.getLineCount() }
 669	 *
 670	 * Note: length is always even.
 671	 */
 672	private int[] fvm;
 673	private int fvmcount;
 674
 675	private int lastfvmget = -1;
 676
 677	//{{{ DisplayManager constructor
 678	private DisplayManager(Buffer buffer, JEditTextArea textArea)
 679	{
 680		this.buffer = buffer;
 681		this.lineMgr = buffer._getLineManager();
 682		this.textArea = textArea;
 683
 684		scrollLineCount = new ScrollLineCount();
 685		firstLine = new FirstLine();
 686
 687		bufferChangeHandler = new BufferChangeHandler();
 688		// this listener priority thing is a bad hack...
 689		buffer.addBufferChangeListener(bufferChangeHandler,
 690			Buffer.HIGH_PRIORITY);
 691	} //}}}
 692
 693	//{{{ dispose() method
 694	private void dispose()
 695	{
 696		buffer.removeBufferChangeListener(bufferChangeHandler);
 697	} //}}}
 698
 699	//{{{ fvmreset() method
 700	private void fvmreset()
 701	{
 702		lastfvmget = -1;
 703		fvmcount = 2;
 704		fvm[0] = 0;
 705		fvm[1] = buffer.getLineCount();
 706	} //}}}
 707
 708	//{{{ fvmtest() method
 709	/**
 710	 * Debug.
 711	 */
 712	private void fvmtest()
 713	{
 714		boolean[] map = new boolean[buffer.getLineCount()];
 715		java.util.Random random = new java.util.Random();
 716
 717		// randomly show and hide lines, both in the fvm structure and
 718		// a simple boolean map
 719		for(int i = 0; i < 1000; i++)
 720		{
 721			int random1 = Math.abs(random.nextInt() % map.length);
 722			int random2 = Math.abs(random.nextInt() % map.length);
 723			if(random2 < random1)
 724			{
 725				int tmp = random1;
 726				random1 = random2;
 727				random2 = tmp;
 728			}
 729
 730			boolean vis = (random1 + random2 % 2 == 0);
 731			if(vis)
 732				showLineRange(random1,random2);
 733			else
 734				hideLineRange(random1,random2);
 735			for(int j = random1; j <= random2; j++)
 736			{
 737				map[j] = vis;
 738			}
 739		}
 740
 741		// check for the fvm structure is correct
 742		for(int i = 0; i < map.length; i++)
 743		{
 744			if(isLineVisible(i) != map[i])
 745				Log.log(Log.ERROR,this,"Mismatch: " + i);
 746		}
 747	} //}}}
 748
 749	//{{{ fvmget() method
 750	/**
 751	 * Returns the fold visibility map index for the given line.
 752	 */
 753	private int fvmget(int line)
 754	{
 755		scanCount++;
 756
 757		if(line < fvm[0])
 758			return -1;
 759		if(line >= fvm[fvmcount - 1])
 760			return fvmcount - 1;
 761
 762		if(lastfvmget != -1)
 763		{
 764			if(line >= fvm[lastfvmget])
 765			{
 766				if(lastfvmget == fvmcount - 1
 767					|| line < fvm[lastfvmget + 1])
 768				{
 769					return lastfvmget;
 770				}
 771			}
 772		}
 773
 774		int start = 0;
 775		int end = fvmcount - 1;
 776
 777loop:		for(;;)
 778		{
 779			scannedLines++;
 780			switch(end - start)
 781			{
 782			case 0:
 783				lastfvmget = start;
 784				break loop;
 785			case 1:
 786				int value = fvm[end];
 787				if(value <= line)
 788					lastfvmget = end;
 789				else
 790					lastfvmget = start;
 791				break loop;
 792			default:
 793				int pivot = (end + start) / 2;
 794				value = fvm[pivot];
 795				if(value == line)
 796				{
 797					lastfvmget = pivot;
 798					break loop;
 799				}
 800				else if(value < line)
 801					start = pivot;
 802				else
 803					end = pivot - 1;
 804				break;
 805			}
 806		}
 807
 808		return lastfvmget;
 809	} //}}}
 810
 811	//{{{ fvmput() method
 812	/**
 813	 * Replaces from <code>start</code> to <code>end-1</code> inclusive with
 814	 * <code>put</code>. Update <code>fvmcount</code>.
 815	 */
 816	private void fvmput(int start, int end, int[] put)
 817	{
 818		if(Debug.FOLD_VIS_DEBUG)
 819		{
 820			StringBuffer buf = new StringBuffer("{");
 821			if(put != null)
 822			{
 823				for(int i = 0; i < put.length; i++)
 824				{
 825					if(i != 0)
 826						buf.append(',');
 827					buf.append(put[i]);
 828				}
 829			}
 830			buf.append("}");
 831			Log.log(Log.DEBUG,this,"fvmput(" + start + ","
 832				+ end + "," + buf + ")");
 833		}
 834		int putl = (put == null ? 0 : put.length);
 835
 836		int delta = putl - (end - start);
 837		if(fvmcount + delta > fvm.length)
 838		{
 839			int[] newfvm = new int[fvm.length * 2 + 1];
 840			System.arraycopy(fvm,0,newfvm,0,fvmcount);
 841			fvm = newfvm;
 842		}
 843
 844		if(delta != 0)
 845		{
 846			System.arraycopy(fvm,end,fvm,start + putl,
 847				fvmcount - end);
 848		}
 849
 850		if(putl != 0)
 851		{
 852			System.arraycopy(put,0,fvm,start,put.length);
 853		}
 854
 855		fvmcount += delta;
 856		if(fvmcount == 0)
 857			throw new InternalError();
 858	} //}}}
 859
 860	//{{{ fvmput2() method
 861	/**
 862	 * Merge previous and next entry if necessary.
 863	 */
 864	private void fvmput2(int starti, int endi, int start, int end)
 865	{
 866		if(Debug.FOLD_VIS_DEBUG)
 867		{
 868			Log.log(Log.DEBUG,this,"*fvmput2(" + starti + ","
 869				+ endi + "," + start + "," + end + ")");
 870		}
 871		if(starti != -1 && fvm[starti] == start)
 872		{
 873			if(endi <= fvmcount - 2 && fvm[endi + 1]
 874				== end + 1)
 875			{
 876				fvmput(starti,endi + 2,null);
 877			}
 878			else
 879			{
 880				fvmput(starti,endi + 1,
 881					new int[] { end + 1 });
 882			}
 883		}
 884		else
 885		{
 886			if(endi != fvmcount - 1 && fvm[endi + 1]
 887				== end + 1)
 888			{
 889				fvmput(starti + 1,endi + 2,
 890					new int[] { start });
 891			}
 892			else
 893			{
 894				fvmput(starti + 1,endi + 1,
 895					new int[] { start,
 896					end + 1 });
 897			}
 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(end != 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		for(int i = start; i <= end; i++)
 976		{
 977			//XXX
 978			if(isLineVisible(i))
 979			{
 980				int screenLines = lineMgr
 981					.getScreenLineCount(i);
 982				if(firstLine.physicalLine >= i)
 983				{
 984					firstLine.scrollLine -= screenLines;
 985					firstLine.callChanged = true;
 986				}
 987				scrollLineCount.scrollLine -= screenLines;
 988				scrollLineCount.callChanged = true;
 989			}
 990		}
 991
 992		/* update fold visibility map. */
 993		int starti = fvmget(start);
 994		int endi = fvmget(end);
 995
 996		if(starti % 2 == 0)
 997		{
 998			if(endi % 2 == 0)
 999				fvmput2(starti,endi,start,end);
1000			else
1001			{
1002				fvmput(starti + 1,endi,null);
1003				fvm[starti + 1] = start;
1004			}
1005		}
1006		else
1007		{
1008			if(endi % 2 == 0)
1009			{
1010				fvmput(starti + 1,endi,null);
1011				fvm[starti + 1] = end + 1;
1012			}
1013			else
1014				fvmput(starti + 1,endi + 1,null);
1015		}
1016
1017		lastfvmget = -1;
1018	} //}}}
1019
1020	//{{{ _setScreenLineCount() method
1021	private void _setScreenLineCount(int line, int oldCount, int count)
1022	{
1023		if(!isLineVisible(line))
1024			return;
1025
1026		if(firstLine.physicalLine >= line)
1027		{
1028			if(firstLine.physicalLine == line)
1029				firstLine.callChanged = true;
1030			else
1031			{
1032				firstLine.scrollLine += (count - oldCount);
1033				firstLine.callChanged = true;
1034			}
1035		}
1036
1037		scrollLineCount.scrollLine += (count - oldCount);
1038		scrollLineCount.callChanged = true;
1039	} //}}}
1040
1041	//}}}
1042
1043	//{{{ Anchor class
1044	static abstract class Anchor
1045	{
1046		int physicalLine;
1047		int scrollLine;
1048		boolean callChanged;
1049		boolean callReset;
1050
1051		abstract void reset();
1052		abstract void changed();
1053	} //}}}
1054
1055	//{{{ ScrollLineCount class
1056	class ScrollLineCount extends Anchor
1057	{
1058		//{{{ changed() method
1059		public void changed()
1060		{
1061			if(Debug.SCROLL_DEBUG)
1062				Log.log(Log.DEBUG,this,"changed()");
1063			textArea.updateScrollBars();
1064			textArea.recalculateLastPhysicalLine();
1065		} //}}}
1066
1067		//{{{ reset() method
1068		public void reset()
1069		{
1070			if(Debug.SCROLL_DEBUG)
1071				Log.log(Log.DEBUG,this,"reset()");
1072
1073			updateWrapSettings();
1074
1075			physicalLine = getFirstVisibleLine();
1076			scrollLine = 0;
1077			while(physicalLine != -1)
1078			{
1079				scrollLine += getScreenLineCount(physicalLine);
1080				physicalLine = getNextVisibleLine(physicalLine);
1081			}
1082
1083			physicalLine = buffer.getLineCount();
1084
1085			firstLine.ensurePhysicalLineIsVisible();
1086
1087			textArea.recalculateLastPhysicalLine();
1088			textArea.updateScrollBars();
1089		} //}}}
1090	} //}}}
1091
1092	//{{{ FirstLine class
1093	class FirstLine extends Anchor
1094	{
1095		int skew;
1096
1097		//{{{ changed() method
1098		public void changed()
1099		{
1100			//{{{ Debug code
1101			if(Debug.SCROLL_DEBUG)
1102			{
1103				Log.log(Log.DEBUG,this,"changed() before: "
1104					+ physicalLine + ":" + scrollLine);
1105			} //}}}
1106
1107			ensurePhysicalLineIsVisible();
1108
1109			int screenLines = getScreenLineCount(physicalLine);
1110			if(skew >= screenLines)
1111				skew = screenLines - 1;
1112
1113			//{{{ Debug code
1114			if(Debug.VERIFY_FIRST_LINE)
1115			{
1116				int verifyScrollLine = 0;
1117
1118				for(int i = 0; i < buffer.getLineCount(); i++)
1119				{
1120					if(!isLineVisible(i))
1121						continue;
1122
1123					if(i >= physicalLine)
1124						break;
1125
1126					verifyScrollLine += getScreenLineCount(i);
1127				}
1128
1129				if(verifyScrollLine != scrollLine)
1130				{
1131					Exception ex = new Exception(scrollLine + ":" + verifyScrollLine);
1132					Log.log(Log.ERROR,this,ex);
1133					new org.gjt.sp.jedit.gui.BeanShellErrorDialog(null,ex);
1134				}
1135			}
1136
1137			if(Debug.SCROLL_DEBUG)
1138			{
1139				Log.log(Log.DEBUG,this,"changed() after: "
1140					+ physicalLine + ":" + scrollLine);
1141			} //}}}
1142
1143			textArea.updateScrollBars();
1144			textArea.recalculateLastPhysicalLine();
1145		} //}}}
1146
1147		//{{{ reset() method
1148		public void reset()
1149		{
1150			if(Debug.SCROLL_DEBUG)
1151				Log.log(Log.DEBUG,this,"reset()");
1152
1153			String wrap = buffer.getStringProperty("wrap");
1154			softWrap = wrap.equals("soft");
1155			if(textArea.maxLineLen <= 0)
1156			{
1157				softWrap = false;
1158				wrapMargin = 0;
1159			}
1160			else
1161			{
1162				// stupidity
1163				char[] foo = new char[textArea.maxLineLen];
1164				for(int i = 0; i < foo.length; i++)
1165				{
1166					foo[i] = ' ';
1167				}
1168				TextAreaPainter painter = textArea.getPainter();
1169				wrapMargin = (int)painter.getFont().getStringBounds(
1170					foo,0,foo.length,
1171					painter.getFontRenderContext())
1172					.getWidth();
1173			}
1174
1175			scrollLine = 0;
1176
1177			int i = getFirstVisibleLine();
1178
1179			for(;;)
1180			{
1181				if(i >= physicalLine)
1182					break;
1183
1184				scrollLine += getScreenLineCount(i);
1185
1186				int nextLine = getNextVisibleLine(i);
1187				if(nextLine == -1)
1188					break;
1189				else
1190					i = nextLine;
1191			}
1192
1193			physicalLine = i;
1194
1195			int screenLines = getScreenLineCount(physicalLine);
1196			if(skew >= screenLines)
1197				skew = screenLines - 1;
1198
1199			textArea.updateScrollBars();
1200		} //}}}
1201
1202		//{{{ physDown() method
1203		// scroll down by physical line amount
1204		void physDown(int amount, int screenAmount)
1205		{
1206			if(Debug.SCROLL_DEBUG)
1207			{
1208				Log.log(Log.DEBUG,this,"physDown() start: "
1209					+ physicalLine + ":" + scrollLine);
1210			}
1211
1212			skew = 0;
1213
1214			if(!isLineVisible(physicalLine))
1215			{
1216				int lastVisibleLine = getLastVisibleLine();
1217				if(physicalLine > lastVisibleLine)
1218					physicalLine = lastVisibleLine;
1219				else
1220				{
1221					int nextPhysicalLine = getNextVisibleLine(physicalLine);
1222					amount -= (nextPhysicalLine - physicalLine);
1223					scrollLine += getScreenLineCount(physicalLine);
1224					physicalLine = nextPhysicalLine;
1225				}
1226			}
1227
1228			for(;;)
1229			{
1230				int nextPhysicalLine = getNextVisibleLine(
1231					physicalLine);
1232				if(nextPhysicalLine == -1)
1233					break;
1234				else if(nextPhysicalLine > physicalLine + amount)
1235					break;
1236				else
1237				{
1238					scrollLine += getScreenLineCount(physicalLine);
1239					amount -= (nextPhysicalLine - physicalLine);
1240					physicalLine = nextPhysicalLine;
1241				}
1242			}
1243
1244			if(Debug.SCROLL_DEBUG)
1245			{
1246				Log.log(Log.DEBUG,this,"physDown() end: "
1247					+ physicalLine + ":" + scrollLine);
1248			}
1249
1250			// JEditTextArea.scrollTo() needs this to simplify
1251			// its code
1252			if(screenAmount < 0)
1253				scrollUp(-screenAmount);
1254			else if(screenAmount > 0)
1255				scrollDown(screenAmount);
1256		} //}}}
1257
1258		//{{{ physUp() method
1259		// scroll up by physical line amount
1260		void physUp(int amount, int screenAmount)
1261		{
1262			if(Debug.SCROLL_DEBUG)
1263			{
1264				Log.log(Log.DEBUG,this,"physUp() start: "
1265					+ physicalLine + ":" + scrollLine);
1266			}
1267
1268			skew = 0;
1269
1270			if(!isLineVisible(physicalLine))
1271			{
1272				int firstVisibleLine = getFirstVisibleLine();
1273				if(physicalLine < firstVisibleLine)
1274					physicalLine = firstVisibleLine;
1275				else
1276				{
1277					int prevPhysicalLine = getPrevVisibleLine(physicalLine);
1278					amount -= (physicalLine - prevPhysicalLine);
1279				}
1280			}
1281
1282			for(;;)
1283			{
1284				int prevPhysicalLine = getPrevVisibleLine(
1285					physicalLine);
1286				if(prevPhysicalLine == -1)
1287					break;
1288				else if(prevPhysicalLine < physicalLine - amount)
1289					break;
1290				else
1291				{
1292					amount -= (physicalLine - prevPhysicalLine);
1293					physicalLine = prevPhysicalLine;
1294					scrollLine -= getScreenLineCount(
1295						prevPhysicalLine);
1296				}
1297			}
1298
1299			if(Debug.SCROLL_DEBUG)
1300			{
1301				Log.log(Log.DEBUG,this,"physUp() end: "
1302					+ physicalLine + ":" + scrollLine);
1303			}
1304
1305			// JEditTextArea.scrollTo() needs this to simplify
1306			// its code
1307			if(screenAmount < 0)
1308				scrollUp(-screenAmount);
1309			else if(screenAmount > 0)
1310				scrollDown(screenAmount);
1311		} //}}}
1312
1313		//{{{ scrollDown() method
1314		// scroll down by screen line amount
1315		void scrollDown(int amount)
1316		{
1317			if(Debug.SCROLL_DEBUG)
1318				Log.log(Log.DEBUG,this,"scrollDown()");
1319
1320			ensurePhysicalLineIsVisible();
1321
1322			amount += skew;
1323
1324			skew = 0;
1325
1326			while(amount > 0)
1327			{
1328				int screenLines = getScreenLineCount(physicalLine);
1329				if(amount < screenLines)
1330				{
1331					skew = amount;
1332					break;
1333				}
1334				else
1335				{
1336					int nextLine = getNextVisibleLine(physicalLine);
1337					if(nextLine == -1)
1338						break;
1339					boolean visible = isLineVisible(physicalLine);
1340					physicalLine = nextLine;
1341					if(visible)
1342					{
1343						amount -= screenLines;
1344						scrollLine += screenLines;
1345					}
1346				}
1347			}
1348		} //}}}
1349
1350		//{{{ scrollUp() method
1351		// scroll up by screen line amount
1352		void scrollUp(int amount)
1353		{
1354			if(Debug.SCROLL_DEBUG)
1355				Log.log(Log.DEBUG,this,"scrollUp()");
1356
1357			ensurePhysicalLineIsVisible();
1358
1359			if(amount <= skew)
1360			{
1361				skew -= amount;
1362			}
1363			else
1364			{
1365				amount -= skew;
1366				skew = 0;
1367
1368				while(amount > 0)
1369				{
1370					int prevLine = getPrevVisibleLine(physicalLine);
1371					if(prevLine == -1)
1372						break;
1373					physicalLine = prevLine;
1374
1375					int screenLines = getScreenLineCount(physicalLine);
1376					scrollLine -= screenLines;
1377					if(amount < screenLines)
1378					{
1379						skew = screenLines - amount;
1380						break;
1381					}
1382					else
1383						amount -= screenLines;
1384				}
1385			}
1386		} //}}}
1387
1388		//{{{ ensurePhysicalLineIsVisible() method
1389		private void ensurePhysicalLineIsVisible()
1390		{
1391			if(!isLineVisible(physicalLine))
1392			{
1393				if(physicalLine > getLastVisibleLine())
1394				{
1395					physicalLine = getPrevVisibleLine(physicalLine);
1396					scrollLine -= getScreenLineCount(physicalLine);
1397				}
1398				else
1399				{
1400					physicalLine = getNextVisibleLine(physicalLine);
1401					scrollLine += getScreenLineCount(physicalLine);
1402				}
1403			}
1404		} //}}}
1405	} //}}}
1406
1407	//{{{ BufferChangeHandler class
1408	class BufferChangeHandler extends BufferChangeAdapter
1409	{
1410		boolean delayedUpdate;
1411		int delayedUpdateStart;
1412		int delayedUpdateEnd;
1413
1414		//{{{ foldHandlerChanged() method
1415		public void foldHandlerChanged(Buffer buffer)
1416		{
1417			fvmreset();
1418
1419			firstLine.callReset = true;
1420			scrollLineCount.callReset = true;
1421
1422			int collapseFolds = buffer.getIntegerProperty(
1423				"collapseFolds",0);
1424			if(collapseFolds != 0)
1425				expandFolds(collapseFolds);
1426
1427			_notifyScreenLineChanges();
1428		} //}}}
1429
1430		//{{{ wrapModeChanged() method
1431		public void wrapModeChanged(Buffer buffer)
1432		{
1433			firstLine.reset();
1434			scrollLineCount.reset();
1435		} //}}}
1436
1437		//{{{ contentInserted() method
1438		public void contentInserted(Buffer buffer, int startLine,
1439			int offset, int numLines, int length)
1440		{
1441			if(!buffer.isLoaded())
1442				return;
1443
1444			delayedUpdate(startLine,startLine + numLines);
1445
1446			if(numLines != 0)
1447			{
1448				contentInserted(firstLine,startLine,numLines);
1449				contentInserted(scrollLineCount,startLine,numLines);
1450
1451				int index = fvmget(startLine);
1452				for(int i = index + 1; i < fvmcount; i++)
1453					fvm[i] += numLines;
1454
1455				lastfvmget = -1;
1456			}
1457
1458			if(!buffer.isTransactionInProgress())
1459				transactionComplete(buffer);
1460		} //}}}
1461
1462		//{{{ preContentRemoved() method
1463		public void preContentRemoved(Buffer buffer, int startLine,
1464			int offset, int numLines, int length)
1465		{
1466			if(!buffer.isLoaded())
1467				return;
1468
1469			if(numLines != 0)
1470			{
1471				preContentRemoved(firstLine,startLine,numLines);
1472				preContentRemoved(scrollLineCount,startLine,numLines);
1473
1474				/* update fold visibility map. */
1475				int starti = fvmget(startLine);
1476				int endi = fvmget(startLine + numLines);
1477
1478				/* both have same visibility; just remove
1479				 * anything in between. */
1480				if(Math.abs(starti % 2) == Math.abs(endi % 2))
1481				{
1482					if(endi - starti == fvmcount)
1483					{
1484						// we're removing from before
1485						// the first visible to after
1486						// the last visible
1487						fvmreset();
1488						firstLine.callReset = true;
1489						scrollLineCount.callReset = true;
1490					}
1491					else
1492					{
1493						fvmput(starti + 1,endi + 1,null);
1494						starti++;
1495					}
1496				}
1497				else
1498				{
1499					fvmput(starti + 1,endi,null);
1500					fvm[starti + 1] = startLine;
1501					starti += 2;
1502				}
1503
1504				for(int i = starti; i < fvmcount; i++)
1505					fvm[i] -= numLines;
1506
1507				lastfvmget = -1;
1508			}
1509		} //}}}
1510
1511		//{{{ contentRemoved() method
1512		public void contentRemoved(Buffer buffer, int startLine,
1513			int offset, int numLines, int length)
1514		{
1515			if(!buffer.isLoaded())
1516				return;
1517
1518			delayedUpdate(startLine,startLine);
1519
1520			if(!buffer.isTransactionInProgress())
1521				transactionComplete(buffer);
1522		} //}}}
1523
1524		//{{{ transactionComplete() method
1525		public void transactionComplete(Buffer buffer)
1526		{
1527			if(delayedUpdate)
1528			{
1529				if(textArea.getDisplayManager() == DisplayManager.this)
1530				{
1531					int firstLine = textArea.getFirstPhysicalLine();
1532					int lastLine = textArea.getLastPhysicalLine();
1533
1534					int line = delayedUpdateStart;
1535					if(!isLineVisible(line))
1536						line = getNextVisibleLine(line);
1537					while(line != -1 && line <= delayedUpdateEnd)
1538					{
1539						if(line < firstLine
1540							|| line > lastLine)
1541						{
1542							getScreenLineCount(line);
1543						}
1544						line = getNextVisibleLine(line);
1545					}
1546				}
1547				_notifyScreenLineChanges();
1548
1549				delayedUpdate = false;
1550			}
1551		} //}}}
1552
1553		//{{{ contentInserted() method
1554		private void contentInserted(Anchor anchor, int startLine,
1555			int numLines)
1556		{
1557			if(anchor.physicalLine >= startLine)
1558			{
1559				if(anchor.physicalLine != startLine)
1560					anchor.physicalLine += numLines;
1561				anchor.callChanged = true;
1562			}
1563		} //}}}
1564
1565		//{{{ preContentRemoved() method
1566		private void preContentRemoved(Anchor anchor, int startLine,
1567			int numLines)
1568		{
1569			if(anchor.physicalLine >= startLine)
1570			{
1571				if(anchor.physicalLine == startLine)
1572					anchor.callChanged = true;
1573				else
1574				{
1575					int end = Math.min(startLine + numLines,
1576						anchor.physicalLine);
1577					for(int i = startLine; i < end; i++)
1578					{
1579						//XXX
1580						if(isLineVisible(i))
1581						{
1582							anchor.scrollLine -=
1583								lineMgr
1584								.getScreenLineCount(i);
1585						}
1586					}
1587					anchor.physicalLine -= (end - startLine);
1588					anchor.callChanged = true;
1589				}
1590			}
1591		} //}}}
1592
1593		//{{{ delayedUpdate() method
1594		private void delayedUpdate(int startLine, int endLine)
1595		{
1596			textArea.chunkCache.invalidateChunksFromPhys(startLine);
1597			if(!delayedUpdate)
1598			{
1599				delayedUpdateStart = startLine;
1600				delayedUpdateEnd = endLine;
1601				delayedUpdate = true;
1602			}
1603			else
1604			{
1605				delayedUpdateStart = Math.min(
1606					delayedUpdateStart,
1607					startLine);
1608				delayedUpdateEnd = Math.max(
1609					delayedUpdateEnd,
1610					endLine);
1611			}
1612		} //}}}
1613	} //}}}
1614}