PageRenderTime 166ms CodeModel.GetById 104ms app.highlight 52ms RepoModel.GetById 1ms app.codeStats 0ms

/jEdit/tags/jedit-4-5-pre1/org/gjt/sp/jedit/buffer/JEditBuffer.java

#
Java | 2863 lines | 1719 code | 333 blank | 811 comment | 279 complexity | 11216d492b980f13a8d1b081d0cdd7b6 MD5 | raw file

Large files files are truncated, but you can click here to view the full file

   1/*
   2 * JEditBuffer.java - jEdit buffer
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1998, 2005 Slava Pestov
   7 * Portions copyright (C) 1999, 2000 mike dillon
   8 *
   9 * This program is free software; you can redistribute it and/or
  10 * modify it under the terms of the GNU General Public License
  11 * as published by the Free Software Foundation; either version 2
  12 * of the License, or any later version.
  13 *
  14 * This program is distributed in the hope that it will be useful,
  15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
  16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
  17 * GNU General Public License for more details.
  18 *
  19 * You should have received a copy of the GNU General Public License
  20 * along with this program; if not, write to the Free Software
  21 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
  22 */
  23
  24package org.gjt.sp.jedit.buffer;
  25
  26//{{{ Imports
  27import org.gjt.sp.jedit.Debug;
  28import org.gjt.sp.jedit.Mode;
  29import org.gjt.sp.jedit.TextUtilities;
  30import org.gjt.sp.jedit.indent.IndentAction;
  31import org.gjt.sp.jedit.indent.IndentRule;
  32import org.gjt.sp.jedit.syntax.*;
  33import org.gjt.sp.jedit.textarea.ColumnBlock;
  34import org.gjt.sp.jedit.textarea.ColumnBlockLine;
  35import org.gjt.sp.jedit.textarea.Node;
  36import org.gjt.sp.jedit.textarea.TextArea;
  37import org.gjt.sp.util.IntegerArray;
  38import org.gjt.sp.util.Log;
  39import org.gjt.sp.util.StandardUtilities;
  40
  41import javax.swing.text.Position;
  42import javax.swing.text.Segment;
  43import java.awt.*;
  44import java.util.*;
  45import java.util.List;
  46import java.util.concurrent.locks.ReentrantReadWriteLock;
  47import java.util.regex.Pattern;
  48//}}}
  49
  50/**
  51 * A <code>JEditBuffer</code> represents the contents of an open text
  52 * file as it is maintained in the computer's memory (as opposed to
  53 * how it may be stored on a disk).<p>
  54 *
  55 * This class is partially thread-safe, however you must pay attention to two
  56 * very important guidelines:
  57 * <ul>
  58 * <li>Operations such as insert() and remove(),
  59 * undo(), change Buffer data in a writeLock(), and must
  60 * be called from the AWT thread.
  61 * <li>When accessing the buffer from another thread, you must
  62 * call readLock() before and readUnLock() after,  if you plan on performing
  63 * more than one read, to ensure that  the buffer contents are not changed by
  64 * the AWT thread for the duration of the lock. Only methods whose descriptions
  65 * specify thread safety can be invoked from other threads.
  66 * </ul>
  67 *
  68 * @author Slava Pestov
  69 * @version $Id: JEditBuffer.java 20108 2011-10-18 12:16:38Z evanpw $
  70 *
  71 * @since jEdit 4.3pre3
  72 */
  73public class JEditBuffer
  74{
  75	/**
  76	 * Line separator property.
  77	 */
  78	public static final String LINESEP = "lineSeparator";
  79
  80	/**
  81	 * Character encoding used when loading and saving.
  82	 * @since jEdit 3.2pre4
  83	 */
  84	public static final String ENCODING = "encoding";
  85
  86	//{{{ JEditBuffer constructors
  87	public JEditBuffer(Map props)
  88	{
  89		bufferListeners = new Vector<Listener>();
  90		lock = new ReentrantReadWriteLock();
  91		contentMgr = new ContentManager();
  92		lineMgr = new LineManager();
  93		positionMgr = new PositionManager(this);
  94		undoMgr = new UndoManager(this);
  95		integerArray = new IntegerArray();
  96		propertyLock = new Object();
  97		properties = new HashMap<Object, PropValue>();
  98
  99		//{{{ need to convert entries of 'props' to PropValue instances
 100		Set<Map.Entry> set = props.entrySet();
 101		for (Map.Entry entry : set)
 102		{
 103			properties.put(entry.getKey(),new PropValue(entry.getValue(),false));
 104		} //}}}
 105
 106		// fill in defaults for these from system properties if the
 107		// corresponding buffer.XXX properties not set
 108		if(getProperty(ENCODING) == null)
 109			properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
 110		if(getProperty(LINESEP) == null)
 111			properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
 112	}
 113
 114	/**
 115	 * Create a new JEditBuffer.
 116	 * It is used by independent textarea only
 117	 */
 118	public JEditBuffer()
 119	{
 120		bufferListeners = new Vector<Listener>();
 121		lock = new ReentrantReadWriteLock();
 122		contentMgr = new ContentManager();
 123		lineMgr = new LineManager();
 124		positionMgr = new PositionManager(this);
 125		undoMgr = new UndoManager(this);
 126		integerArray = new IntegerArray();
 127		propertyLock = new Object();
 128		properties = new HashMap<Object, PropValue>();
 129
 130		properties.put("wrap",new PropValue("none",false));
 131		properties.put("folding",new PropValue("none",false));
 132		tokenMarker = new TokenMarker();
 133		tokenMarker.addRuleSet(new ParserRuleSet("text","MAIN"));
 134		setTokenMarker(tokenMarker);
 135
 136		loadText(null,null);
 137		// corresponding buffer.XXX properties not set
 138		if(getProperty(ENCODING) == null)
 139			properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
 140		if(getProperty(LINESEP) == null)
 141			properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
 142
 143		setFoldHandler(new DummyFoldHandler());
 144	} //}}}
 145
 146	//{{{ Flags
 147
 148	//{{{ isDirty() method
 149	/**
 150	 * Returns whether there have been unsaved changes to this buffer.
 151	 * This method is thread-safe.
 152	 */
 153	public boolean isDirty()
 154	{
 155		return dirty;
 156	} //}}}
 157
 158	//{{{ isLoading() method
 159	public boolean isLoading()
 160	{
 161		return loading;
 162	} //}}}
 163
 164	//{{{ setLoading() method
 165	public void setLoading(boolean loading)
 166	{
 167		this.loading = loading;
 168	} //}}}
 169
 170	//{{{ isPerformingIO() method
 171	/**
 172	 * Returns true if the buffer is currently performing I/O.
 173	 * This method is thread-safe.
 174	 * @since jEdit 2.7pre1
 175	 */
 176	public boolean isPerformingIO()
 177	{
 178		return isLoading() || io;
 179	} //}}}
 180
 181	//{{{ setPerformingIO() method
 182	/**
 183	 * Returns true if the buffer is currently performing I/O.
 184	 * This method is thread-safe.
 185	 * @since jEdit 2.7pre1
 186	 */
 187	public void setPerformingIO(boolean io)
 188	{
 189		this.io = io;
 190	} //}}}
 191
 192	//{{{ isEditable() method
 193	/**
 194	 * Returns true if this file is editable, false otherwise. A file may
 195	 * become uneditable if it is read only, or if I/O is in progress.
 196	 * This method is thread-safe.
 197	 * @since jEdit 2.7pre1
 198	 */
 199	public boolean isEditable()
 200	{
 201		return !(isReadOnly() || isPerformingIO());
 202	} //}}}
 203
 204	//{{{ isReadOnly() method
 205	/**
 206	 * Returns true if this file is read only, false otherwise.
 207	 * This method is thread-safe.
 208	 */
 209	public boolean isReadOnly()
 210	{
 211		return readOnly || readOnlyOverride;
 212	} //}}}
 213
 214	//{{{ setReadOnly() method
 215	/**
 216	 * Sets the read only flag.
 217	 * @param readOnly The read only flag
 218	 */
 219	public void setReadOnly(boolean readOnly)
 220	{
 221		readOnlyOverride = readOnly;
 222	} //}}}
 223
 224	//{{{ setDirty() method
 225	/**
 226	 * Sets the 'dirty' (changed since last save) flag of this buffer.
 227	 */
 228	public void setDirty(boolean d)
 229	{
 230		boolean editable = isEditable();
 231
 232		if(d)
 233		{
 234			if(editable)
 235				dirty = true;
 236		}
 237		else
 238		{
 239			dirty = false;
 240
 241			// fixes dirty flag not being reset on
 242			// save/insert/undo/redo/undo
 243			if(!isUndoInProgress())
 244			{
 245				// this ensures that undo can clear the dirty flag properly
 246				// when all edits up to a save are undone
 247				undoMgr.resetClearDirty();
 248			}
 249		}
 250	} //}}}
 251
 252	//}}}
 253
 254	//{{{ Thread safety
 255
 256	//{{{ readLock() method
 257	/**
 258	 * The buffer is guaranteed not to change between calls to
 259	 * {@link #readLock()} and {@link #readUnlock()}.
 260	 */
 261	public void readLock()
 262	{
 263		lock.readLock().lock();
 264	} //}}}
 265
 266	//{{{ readUnlock() method
 267	/**
 268	 * The buffer is guaranteed not to change between calls to
 269	 * {@link #readLock()} and {@link #readUnlock()}.
 270	 */
 271	public void readUnlock()
 272	{
 273		lock.readLock().unlock();
 274	} //}}}
 275
 276	//{{{ writeLock() method
 277	/**
 278	 * Attempting to obtain read lock will block between calls to
 279	 * {@link #writeLock()} and {@link #writeUnlock()}.
 280	 */
 281	public void writeLock()
 282	{
 283		lock.writeLock().lock();
 284	} //}}}
 285
 286	//{{{ writeUnlock() method
 287	/**
 288	 * Attempting to obtain read lock will block between calls to
 289	 * {@link #writeLock()} and {@link #writeUnlock()}.
 290	 */
 291	public void writeUnlock()
 292	{
 293		lock.writeLock().unlock();
 294	} //}}}
 295
 296	//}}}
 297
 298	//{{{ Line offset methods
 299
 300	//{{{ getLength() method
 301	/**
 302	 * Returns the number of characters in the buffer. This method is thread-safe.
 303	 */
 304	public int getLength()
 305	{
 306		// no need to lock since this just returns a value and that's it
 307		return contentMgr.getLength();
 308	} //}}}
 309
 310	//{{{ getLineCount() method
 311	/**
 312	 * Returns the number of physical lines in the buffer.
 313	 * This method is thread-safe.
 314	 * @since jEdit 3.1pre1
 315	 */
 316	public int getLineCount()
 317	{
 318		// no need to lock since this just returns a value and that's it
 319		return lineMgr.getLineCount();
 320	} //}}}
 321
 322	//{{{ getLineOfOffset() method
 323	/**
 324	 * Returns the line containing the specified offset.
 325	 * This method is thread-safe.
 326	 * @param offset The offset
 327	 * @since jEdit 4.0pre1
 328	 */
 329	public int getLineOfOffset(int offset)
 330	{
 331		try
 332		{
 333			readLock();
 334
 335			if(offset < 0 || offset > getLength())
 336				throw new ArrayIndexOutOfBoundsException(offset);
 337
 338			return lineMgr.getLineOfOffset(offset);
 339		}
 340		finally
 341		{
 342			readUnlock();
 343		}
 344	} //}}}
 345
 346	//{{{ getLineStartOffset() method
 347	/**
 348	 * Returns the start offset of the specified line.
 349	 * This method is thread-safe.
 350	 * @param line The line
 351	 * @return The start offset of the specified line
 352	 * @since jEdit 4.0pre1
 353	 */
 354	public int getLineStartOffset(int line)
 355	{
 356		try
 357		{
 358			readLock();
 359
 360			if(line < 0 || line >= lineMgr.getLineCount())
 361				throw new ArrayIndexOutOfBoundsException(line);
 362			else if(line == 0)
 363				return 0;
 364
 365			return lineMgr.getLineEndOffset(line - 1);
 366		}
 367		finally
 368		{
 369			readUnlock();
 370		}
 371	} //}}}
 372
 373	//{{{ getLineEndOffset() method
 374	/**
 375	 * Returns the end offset of the specified line.
 376	 * This method is thread-safe.
 377	 * @param line The line
 378	 * @return The end offset of the specified line
 379	 * invalid.
 380	 * @since jEdit 4.0pre1
 381	 */
 382	public int getLineEndOffset(int line)
 383	{
 384		try
 385		{
 386			readLock();
 387
 388			if(line < 0 || line >= lineMgr.getLineCount())
 389				throw new ArrayIndexOutOfBoundsException(line);
 390
 391			return lineMgr.getLineEndOffset(line);
 392		}
 393		finally
 394		{
 395			readUnlock();
 396		}
 397	} //}}}
 398
 399	//{{{ getLineLength() method
 400	/**
 401	 * Returns the length of the specified line.
 402	 * This method is thread-safe.
 403	 * @param line The line
 404	 * @since jEdit 4.0pre1
 405	 */
 406	public int getLineLength(int line)
 407	{
 408		try
 409		{
 410			readLock();
 411
 412			return getLineEndOffset(line)
 413				- getLineStartOffset(line) - 1;
 414		}
 415		finally
 416		{
 417			readUnlock();
 418		}
 419	} //}}}
 420
 421	//{{{ getPriorNonEmptyLine() method
 422	/**
 423	 * Auto indent needs this.
 424	 */
 425	public int getPriorNonEmptyLine(int lineIndex)
 426	{
 427		if (!mode.getIgnoreWhitespace())
 428		{
 429			return lineIndex - 1;
 430		}
 431
 432		int returnValue = -1;
 433		for(int i = lineIndex - 1; i >= 0; i--)
 434		{
 435			Segment seg = new Segment();
 436			getLineText(i,seg);
 437			if(seg.count != 0)
 438				returnValue = i;
 439			for(int j = 0; j < seg.count; j++)
 440			{
 441				char ch = seg.array[seg.offset + j];
 442				if(!Character.isWhitespace(ch))
 443					return i;
 444			}
 445		}
 446
 447		// didn't find a line that contains non-whitespace chars
 448		// so return index of prior whitespace line
 449		return returnValue;
 450	} //}}}
 451
 452	//}}}
 453
 454	//{{{ Text getters and setters
 455
 456	//{{{ getLineText() methods
 457	/**
 458	 * Returns the text on the specified line.
 459	 * This method is thread-safe.
 460	 * @param line The line
 461	 * @return The text, or null if the line is invalid
 462	 * @since jEdit 4.0pre1
 463	 */
 464	public String getLineText(int line)
 465	{
 466		if(line < 0 || line >= lineMgr.getLineCount())
 467			throw new ArrayIndexOutOfBoundsException(line);
 468
 469		try
 470		{
 471			readLock();
 472
 473			int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
 474			int end = lineMgr.getLineEndOffset(line);
 475
 476			return getText(start,end - start - 1);
 477		}
 478		finally
 479		{
 480			readUnlock();
 481		}
 482	}
 483
 484	/**
 485	 * Returns the specified line in a <code>Segment</code>.<p>
 486	 *
 487	 * Using a <classname>Segment</classname> is generally more
 488	 * efficient than using a <classname>String</classname> because it
 489	 * results in less memory allocation and array copying.<p>
 490	 *
 491	 * This method is thread-safe.
 492	 *
 493	 * @param line The line
 494	 * @since jEdit 4.0pre1
 495	 */
 496	public void getLineText(int line, Segment segment)
 497	{
 498		getLineText(line, 0, segment);
 499	}
 500
 501	/**
 502	 * Returns the specified line from the starting point passed in relativeStartOffset  in a <code>Segment</code>.<p>
 503	 *
 504	 * Using a <classname>Segment</classname> is generally more
 505	 * efficient than using a <classname>String</classname> because it
 506	 * results in less memory allocation and array copying.<p>
 507	 *
 508	 * This method is thread-safe.
 509	 *
 510	 * @param line The line
 511	 * @since jEdit 4.0pre1
 512	 */
 513	public void getLineText(int line,int relativeStartOffset, Segment segment)
 514	{
 515		if(line < 0 || line >= lineMgr.getLineCount())
 516			throw new ArrayIndexOutOfBoundsException(line);
 517
 518		try
 519		{
 520			readLock();
 521
 522			int start = (line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1)); 
 523			int end = lineMgr.getLineEndOffset(line);
 524			if((start+relativeStartOffset)>end)
 525			{
 526				throw new IllegalArgumentException("This index is outside the line length (start+relativeOffset):"+start+" + "+relativeStartOffset+" > "+"endffset:"+end);
 527			}
 528			else
 529			{	
 530				getText(start+relativeStartOffset,end - start -relativeStartOffset- 1,segment);
 531			}	
 532		}
 533		finally
 534		{
 535			readUnlock();
 536		}
 537	} //}}}
 538	
 539	//{{{ getLineSegment() method
 540	/**
 541	 * Returns the text on the specified line.
 542	 * This method is thread-safe.
 543	 *
 544	 * @param line The line index.
 545	 * @return The text, or null if the line is invalid
 546	 *
 547	 * @since jEdit 4.3pre15
 548	 */
 549	public CharSequence getLineSegment(int line)
 550	{
 551		if(line < 0 || line >= lineMgr.getLineCount())
 552			throw new ArrayIndexOutOfBoundsException(line);
 553
 554		try
 555		{
 556			readLock();
 557
 558			int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
 559			int end = lineMgr.getLineEndOffset(line);
 560
 561			return getSegment(start,end - start - 1);
 562		}
 563		finally
 564		{
 565			readUnlock();
 566		}
 567	} //}}}
 568
 569	//{{{ getText() methods
 570	/**
 571	 * Returns the specified text range. This method is thread-safe.
 572	 * @param start The start offset
 573	 * @param length The number of characters to get
 574	 */
 575	public String getText(int start, int length)
 576	{
 577		try
 578		{
 579			readLock();
 580
 581			if(start < 0 || length < 0
 582				|| start + length > contentMgr.getLength())
 583				throw new ArrayIndexOutOfBoundsException(start + ":" + length);
 584
 585			return contentMgr.getText(start,length);
 586		}
 587		finally
 588		{
 589			readUnlock();
 590		}
 591	}
 592
 593	/**
 594	 * Returns the full buffer content. This method is thread-safe
 595	 * @since 4.4.1
 596	 */
 597	public String getText()
 598	{
 599		try
 600		{
 601			readLock();
 602			return contentMgr.getText(0, getLength());
 603		}
 604		finally
 605		{
 606			readUnlock();
 607		}
 608	}
 609
 610	/**
 611	 * Returns the specified text range in a <code>Segment</code>.<p>
 612	 *
 613	 * Using a <classname>Segment</classname> is generally more
 614	 * efficient than using a <classname>String</classname> because it
 615	 * results in less memory allocation and array copying.<p>
 616	 *
 617	 * This method is thread-safe.
 618	 *
 619	 * @param start The start offset
 620	 * @param length The number of characters to get
 621	 * @param seg The segment to copy the text to
 622	 */
 623	public void getText(int start, int length, Segment seg)
 624	{
 625		try
 626		{
 627			readLock();
 628
 629			if(start < 0 || length < 0
 630				|| start + length > contentMgr.getLength())
 631				throw new ArrayIndexOutOfBoundsException(start + ":" + length);
 632
 633			contentMgr.getText(start,length,seg);
 634		}
 635		finally
 636		{
 637			readUnlock();
 638		}
 639	} //}}}
 640
 641	//{{{ getSegment() method
 642	/**
 643	 * Returns the specified text range. This method is thread-safe.
 644	 * It doesn't copy the text
 645	 *
 646	 * @param start The start offset
 647	 * @param length The number of characters to get
 648	 *
 649	 * @return a CharSequence that contains the text wanted text
 650	 * @since jEdit 4.3pre15
 651	 */
 652	public CharSequence getSegment(int start, int length)
 653	{
 654		try
 655		{
 656			readLock();
 657
 658			if(start < 0 || length < 0
 659				|| start + length > contentMgr.getLength())
 660				throw new ArrayIndexOutOfBoundsException(start + ":" + length);
 661
 662			return contentMgr.getSegment(start,length);
 663		}
 664		finally
 665		{
 666			readUnlock();
 667		}
 668	} //}}}
 669
 670	//{{{ insert() methods
 671	/**
 672	 * Inserts a string into the buffer.
 673	 * @param offset The offset
 674	 * @param str The string
 675	 * @since jEdit 4.0pre1
 676	 */
 677	public void insert(int offset, String str)
 678	{
 679		if(str == null)
 680			return;
 681
 682		int len = str.length();
 683
 684		if(len == 0)
 685			return;
 686
 687		if(isReadOnly())
 688			throw new RuntimeException("buffer read-only");
 689
 690		try
 691		{
 692			writeLock();
 693
 694			if(offset < 0 || offset > contentMgr.getLength())
 695				throw new ArrayIndexOutOfBoundsException(offset);
 696
 697			contentMgr.insert(offset,str);
 698
 699			integerArray.clear();
 700
 701			for(int i = 0; i < len; i++)
 702			{
 703				if(str.charAt(i) == '\n')
 704					integerArray.add(i + 1);
 705			}
 706
 707			if(!undoInProgress)
 708			{
 709				undoMgr.contentInserted(offset,len,str,!dirty);
 710			}
 711
 712			contentInserted(offset,len,integerArray);
 713		}
 714		finally
 715		{
 716			writeUnlock();
 717		}
 718	}
 719
 720	/**
 721	 * Inserts a string into the buffer.
 722	 * @param offset The offset
 723	 * @param seg The segment
 724	 * @since jEdit 4.0pre1
 725	 */
 726	public void insert(int offset, Segment seg)
 727	{
 728		if(seg.count == 0)
 729			return;
 730
 731		if(isReadOnly())
 732			throw new RuntimeException("buffer read-only");
 733
 734		try
 735		{
 736			writeLock();
 737
 738			if(offset < 0 || offset > contentMgr.getLength())
 739				throw new ArrayIndexOutOfBoundsException(offset);
 740
 741			contentMgr.insert(offset,seg);
 742
 743			integerArray.clear();
 744
 745			for(int i = 0; i < seg.count; i++)
 746			{
 747				if(seg.array[seg.offset + i] == '\n')
 748					integerArray.add(i + 1);
 749			}
 750
 751			if(!undoInProgress)
 752			{
 753				undoMgr.contentInserted(offset,seg.count,
 754					seg.toString(),!dirty);
 755			}
 756
 757			contentInserted(offset,seg.count,integerArray);
 758		}
 759		finally
 760		{
 761			writeUnlock();
 762		}
 763	} //}}}
 764
 765	//{{{ remove() method
 766	/**
 767	 * Removes the specified rang efrom the buffer.
 768	 * @param offset The start offset
 769	 * @param length The number of characters to remove
 770	 */
 771	public void remove(int offset, int length)
 772	{
 773		if(length == 0)
 774			return;
 775
 776		if(isReadOnly())
 777			throw new RuntimeException("buffer read-only");
 778
 779		try
 780		{
 781			transaction = true;
 782
 783			writeLock();
 784
 785			if(offset < 0 || length < 0
 786				|| offset + length > contentMgr.getLength())
 787				throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
 788
 789			int startLine = lineMgr.getLineOfOffset(offset);
 790			int endLine = lineMgr.getLineOfOffset(offset + length);
 791
 792			int numLines = endLine - startLine;
 793
 794			if(!undoInProgress && !loading)
 795			{
 796				undoMgr.contentRemoved(offset,length,
 797					getText(offset,length),
 798					!dirty);
 799			}
 800
 801			firePreContentRemoved(startLine,offset,numLines,length);
 802
 803			contentMgr.remove(offset,length);
 804			lineMgr.contentRemoved(startLine,offset,numLines,length);
 805			positionMgr.contentRemoved(offset,length);
 806
 807			setDirty(true);
 808
 809			fireContentRemoved(startLine,offset,numLines,length);
 810
 811			/* otherwise it will be delivered later */
 812			if(!undoInProgress && !insideCompoundEdit())
 813				fireTransactionComplete();
 814
 815		}
 816		finally
 817		{
 818			transaction = false;
 819
 820			writeUnlock();
 821		}
 822	} //}}}
 823
 824	//}}}
 825
 826	//{{{ Indentation
 827
 828	//{{{ removeTrailingWhiteSpace() method
 829	/**
 830	 * Removes trailing whitespace from all lines in the specified list.
 831	 * @param lines The line numbers
 832	 * @since jEdit 3.2pre1
 833	 */
 834	public void removeTrailingWhiteSpace(int[] lines)
 835	{
 836		try
 837		{
 838			beginCompoundEdit();
 839
 840			for(int i = 0; i < lines.length; i++)
 841			{
 842				Segment seg = new Segment();
 843				getLineText(lines[i],seg);
 844
 845				// blank line
 846				if (seg.count == 0) continue;
 847
 848				int lineStart = seg.offset;
 849				int lineEnd = seg.offset + seg.count - 1;
 850
 851				int pos;
 852				for (pos = lineEnd; pos >= lineStart; pos--)
 853				{
 854					if (!Character.isWhitespace(seg.array[pos]))
 855						break;
 856				}
 857
 858				int tail = lineEnd - pos;
 859
 860				// no whitespace
 861				if (tail == 0) continue;
 862
 863				remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
 864			}
 865		}
 866		finally
 867		{
 868			endCompoundEdit();
 869		}
 870	} //}}}
 871
 872	//{{{ shiftIndentLeft() method
 873	/**
 874	 * Shifts the indent of each line in the specified list to the left.
 875	 * @param lines The line numbers
 876	 * @since jEdit 3.2pre1
 877	 */
 878	public void shiftIndentLeft(int[] lines)
 879	{
 880		int tabSize = getTabSize();
 881		int indentSize = getIndentSize();
 882		boolean noTabs = getBooleanProperty("noTabs");
 883
 884		try
 885		{
 886			beginCompoundEdit();
 887
 888			for(int i = 0; i < lines.length; i++)
 889			{
 890				int lineStart = getLineStartOffset(lines[i]);
 891				CharSequence line = getLineSegment(lines[i]);
 892				int whiteSpace = StandardUtilities
 893					.getLeadingWhiteSpace(line);
 894				if(whiteSpace == 0)
 895					continue;
 896				int whiteSpaceWidth = Math.max(0,StandardUtilities
 897					.getLeadingWhiteSpaceWidth(line,tabSize)
 898					- indentSize);
 899
 900				insert(lineStart + whiteSpace,StandardUtilities
 901					.createWhiteSpace(whiteSpaceWidth,
 902					noTabs ? 0 : tabSize));
 903				remove(lineStart,whiteSpace);
 904			}
 905
 906		}
 907		finally
 908		{
 909			endCompoundEdit();
 910		}
 911	} //}}}
 912
 913	//{{{ shiftIndentRight() method
 914	/**
 915	 * Shifts the indent of each line in the specified list to the right.
 916	 * @param lines The line numbers
 917	 * @since jEdit 3.2pre1
 918	 */
 919	public void shiftIndentRight(int[] lines)
 920	{
 921		try
 922		{
 923			beginCompoundEdit();
 924
 925			int tabSize = getTabSize();
 926			int indentSize = getIndentSize();
 927			boolean noTabs = getBooleanProperty("noTabs");
 928			for(int i = 0; i < lines.length; i++)
 929			{
 930				int lineStart = getLineStartOffset(lines[i]);
 931				CharSequence line = getLineSegment(lines[i]);
 932				int whiteSpace = StandardUtilities
 933					.getLeadingWhiteSpace(line);
 934
 935				// silly usability hack
 936				//if(lines.length != 1 && whiteSpace == 0)
 937				//	continue;
 938
 939				int whiteSpaceWidth = StandardUtilities
 940					.getLeadingWhiteSpaceWidth(
 941					line,tabSize) + indentSize;
 942				insert(lineStart + whiteSpace,StandardUtilities
 943					.createWhiteSpace(whiteSpaceWidth,
 944					noTabs ? 0 : tabSize));
 945				remove(lineStart,whiteSpace);
 946			}
 947		}
 948		finally
 949		{
 950			endCompoundEdit();
 951		}
 952	} //}}}
 953
 954	//{{{ indentLines() methods
 955	/**
 956	 * Indents all specified lines.
 957	 * @param start The first line to indent
 958	 * @param end The last line to indent
 959	 * @since jEdit 3.1pre3
 960	 */
 961	public void indentLines(int start, int end)
 962	{
 963		try
 964		{
 965			beginCompoundEdit();
 966			for(int i = start; i <= end; i++)
 967				indentLine(i,true);
 968		}
 969		finally
 970		{
 971			endCompoundEdit();
 972		}
 973	}
 974
 975	/**
 976	 * Indents all specified lines.
 977	 * @param lines The line numbers
 978	 * @since jEdit 3.2pre1
 979	 */
 980	public void indentLines(int[] lines)
 981	{
 982		try
 983		{
 984			beginCompoundEdit();
 985			for(int i = 0; i < lines.length; i++)
 986				indentLine(lines[i],true);
 987		}
 988		finally
 989		{
 990			endCompoundEdit();
 991		}
 992	} //}}}
 993
 994	//{{{ indentLine() methods
 995	/**
 996	 * Indents the specified line.
 997	 * @param lineIndex The line number to indent
 998	 * @param canDecreaseIndent If true, the indent can be decreased as a
 999	 * result of this. Set this to false for Tab key.
1000	 * @return true If indentation took place, false otherwise.
1001	 * @since jEdit 4.2pre2
1002	 */
1003	public boolean indentLine(int lineIndex, boolean canDecreaseIndent)
1004	{
1005		int[] whitespaceChars = new int[1];
1006		int currentIndent = getCurrentIndentForLine(lineIndex,
1007			whitespaceChars);
1008		int prevLineIndex = getPriorNonEmptyLine(lineIndex);
1009		int prevLineIndent = (prevLineIndex == -1) ? 0 :
1010			StandardUtilities.getLeadingWhiteSpaceWidth(getLineSegment(
1011				prevLineIndex), getTabSize());
1012		int idealIndent = getIdealIndentForLine(lineIndex, prevLineIndex,
1013			prevLineIndent);
1014
1015		if (idealIndent == -1 || idealIndent == currentIndent ||
1016			(!canDecreaseIndent && idealIndent < currentIndent))
1017			return false;
1018
1019		// Do it
1020		try
1021		{
1022			beginCompoundEdit();
1023
1024			int start = getLineStartOffset(lineIndex);
1025
1026			remove(start,whitespaceChars[0]);
1027			String prevIndentString = (prevLineIndex >= 0) ?
1028				StandardUtilities.getIndentString(getLineText(
1029					prevLineIndex)) : null;
1030			String indentString;
1031			if (prevIndentString == null)
1032			{
1033				indentString = StandardUtilities.createWhiteSpace(
1034					idealIndent,
1035					getBooleanProperty("noTabs") ? 0 : getTabSize());
1036			}
1037			else if (idealIndent == prevLineIndent)
1038				indentString = prevIndentString;
1039			else if (idealIndent < prevLineIndent)
1040				indentString = StandardUtilities.truncateWhiteSpace(
1041					idealIndent, getTabSize(), prevIndentString);
1042			else
1043				indentString = prevIndentString +
1044					StandardUtilities.createWhiteSpace(
1045						idealIndent - prevLineIndent,
1046						getBooleanProperty("noTabs") ? 0 : getTabSize(),
1047						prevLineIndent);
1048			insert(start, indentString);
1049		}
1050		finally
1051		{
1052			endCompoundEdit();
1053		}
1054
1055		return true;
1056	} //}}}
1057
1058	//{{{ getCurrentIndentForLine() method
1059	/**
1060	 * Returns the line's current leading indent.
1061	 * @param lineIndex The line number
1062	 * @param whitespaceChars If this is non-null, the number of whitespace
1063	 * characters is stored at the 0 index
1064	 * @since jEdit 4.2pre2
1065	 */
1066	public int getCurrentIndentForLine(int lineIndex, int[] whitespaceChars)
1067	{
1068		Segment seg = new Segment();
1069		getLineText(lineIndex,seg);
1070
1071		int tabSize = getTabSize();
1072
1073		int currentIndent = 0;
1074loop:		for(int i = 0; i < seg.count; i++)
1075		{
1076			char c = seg.array[seg.offset + i];
1077			switch(c)
1078			{
1079			case ' ':
1080				currentIndent++;
1081				if(whitespaceChars != null)
1082					whitespaceChars[0]++;
1083				break;
1084			case '\t':
1085				currentIndent += tabSize - (currentIndent
1086					% tabSize);
1087				if(whitespaceChars != null)
1088					whitespaceChars[0]++;
1089				break;
1090			default:
1091				break loop;
1092			}
1093		}
1094
1095		return currentIndent;
1096	} //}}}
1097
1098	//{{{ getIdealIndentForLine() method
1099	/**
1100	 * Returns the ideal leading indent for the specified line.
1101	 * This will apply the various auto-indent rules.
1102	 * @param lineIndex The line number
1103	 */
1104	public int getIdealIndentForLine(int lineIndex)
1105	{
1106		int prevLineIndex = getPriorNonEmptyLine(lineIndex);
1107		int oldIndent = prevLineIndex == -1 ? 0 :
1108			StandardUtilities.getLeadingWhiteSpaceWidth(
1109			getLineSegment(prevLineIndex),
1110			getTabSize());
1111		return getIdealIndentForLine(lineIndex, prevLineIndex,
1112			oldIndent);
1113	} //}}}
1114
1115	//{{{ getIdealIndentForLine() method
1116	/**
1117	 * Returns the ideal leading indent for the specified line.
1118	 * This will apply the various auto-indent rules.
1119	 * @param lineIndex The line number
1120	 * @param prevLineIndex The index of the previous non-empty line
1121	 * @param oldIndent The indent width of the previous line (or 0)
1122	 */
1123	private int getIdealIndentForLine(int lineIndex, int prevLineIndex,
1124		int oldIndent)
1125	{
1126		int prevPrevLineIndex = prevLineIndex < 0 ? -1
1127			: getPriorNonEmptyLine(prevLineIndex);
1128		int newIndent = oldIndent;
1129
1130		List<IndentRule> indentRules = getIndentRules(lineIndex);
1131		List<IndentAction> actions = new LinkedList<IndentAction>();
1132		for (int i = 0;i<indentRules.size();i++)
1133		{
1134			IndentRule rule = indentRules.get(i);
1135			rule.apply(this,lineIndex,prevLineIndex,
1136				prevPrevLineIndex,actions);
1137		}
1138
1139
1140		for (IndentAction action : actions)
1141		{
1142			newIndent = action.calculateIndent(this, lineIndex,
1143					oldIndent, newIndent);
1144			if (!action.keepChecking())
1145				break;
1146		}
1147		if (newIndent < 0)
1148			newIndent = 0;
1149
1150		return newIndent;
1151	} //}}}
1152
1153	//{{{ getVirtualWidth() method
1154	/**
1155	 * Returns the virtual column number (taking tabs into account) of the
1156	 * specified position.
1157	 *
1158	 * @param line The line number
1159	 * @param column The column number
1160	 * @since jEdit 4.1pre1
1161	 */
1162	public int getVirtualWidth(int line, int column)
1163	{
1164		try
1165		{
1166			readLock();
1167
1168			int start = getLineStartOffset(line);
1169			Segment seg = new Segment();
1170			getText(start,column,seg);
1171
1172			return StandardUtilities.getVirtualWidth(seg,getTabSize());
1173		}
1174		finally
1175		{
1176			readUnlock();
1177		}
1178	} //}}}
1179
1180	//{{{ getOffsetOfVirtualColumn() method
1181	/**
1182	 * Returns the offset of a virtual column number (taking tabs
1183	 * into account) relative to the start of the line in question.
1184	 *
1185	 * @param line The line number
1186	 * @param column The virtual column number
1187	 * @param totalVirtualWidth If this array is non-null, the total
1188	 * virtual width will be stored in its first location if this method
1189	 * returns -1.
1190	 *
1191	 * @return -1 if the column is out of bounds
1192	 *
1193	 * @since jEdit 4.1pre1
1194	 */
1195	public int getOffsetOfVirtualColumn(int line, int column,
1196		int[] totalVirtualWidth)
1197	{
1198		try
1199		{
1200			readLock();
1201
1202			Segment seg = new Segment();
1203			getLineText(line,seg);
1204
1205			return StandardUtilities.getOffsetOfVirtualColumn(seg,
1206				getTabSize(),column,totalVirtualWidth);
1207		}
1208		finally
1209		{
1210			readUnlock();
1211		}
1212	} //}}}
1213
1214	//{{{ insertAtColumn() method
1215	/**
1216	 * Like the {@link #insert(int,String)} method, but inserts the string at
1217	 * the specified virtual column. Inserts spaces as appropriate if
1218	 * the line is shorter than the column.
1219	 * @param line The line number
1220	 * @param col The virtual column number
1221	 * @param str The string
1222	 */
1223	public void insertAtColumn(int line, int col, String str)
1224	{
1225		try
1226		{
1227			writeLock();
1228
1229			int[] total = new int[1];
1230			int offset = getOffsetOfVirtualColumn(line,col,total);
1231			if(offset == -1)
1232			{
1233				offset = getLineEndOffset(line) - 1;
1234				str = StandardUtilities.createWhiteSpace(col - total[0],0) + str;
1235			}
1236			else
1237				offset += getLineStartOffset(line);
1238
1239			insert(offset,str);
1240		}
1241		finally
1242		{
1243			writeUnlock();
1244		}
1245	} //}}}
1246
1247	//{{{ insertIndented() method
1248	/**
1249	 * Inserts a string into the buffer, indenting each line of the string
1250	 * to match the indent of the first line.
1251	 *
1252	 * @param offset The offset
1253	 * @param text The text
1254	 *
1255	 * @return The number of characters of indent inserted on each new
1256	 * line. This is used by the abbreviations code.
1257	 *
1258	 * @since jEdit 4.2pre14
1259	 */
1260	public int insertIndented(int offset, String text)
1261	{
1262		try
1263		{
1264			beginCompoundEdit();
1265
1266			// obtain the leading indent for later use
1267			int firstLine = getLineOfOffset(offset);
1268			CharSequence lineText = getLineSegment(firstLine);
1269			int leadingIndent
1270				= StandardUtilities.getLeadingWhiteSpaceWidth(
1271				lineText,getTabSize());
1272
1273			String whiteSpace = StandardUtilities.createWhiteSpace(
1274				leadingIndent,getBooleanProperty("noTabs")
1275				? 0 : getTabSize());
1276
1277			insert(offset,text);
1278
1279			int lastLine = getLineOfOffset(offset + text.length());
1280
1281			// note that if firstLine == lastLine, loop does not
1282			// execute
1283			for(int i = firstLine + 1; i <= lastLine; i++)
1284			{
1285				insert(getLineStartOffset(i),whiteSpace);
1286			}
1287
1288			return whiteSpace.length();
1289		}
1290		finally
1291		{
1292			endCompoundEdit();
1293		}
1294	} //}}}
1295
1296	//{{{ isElectricKey() methods
1297	/**
1298	 * Should inserting this character trigger a re-indent of
1299	 * the current line?
1300	 * @since jEdit 4.3pre9
1301	 */
1302	public boolean isElectricKey(char ch, int line)
1303	{
1304		TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
1305		Mode mode = ModeProvider.instance.getMode(ctx.rules.getModeName());
1306
1307		// mode can be null, though that's probably an error "further up":
1308		if (mode == null)
1309			return false;
1310		return mode.isElectricKey(ch);
1311	} //}}}
1312
1313	//}}}
1314
1315	//{{{ Syntax highlighting
1316
1317	//{{{ markTokens() method
1318	/**
1319	 * Returns the syntax tokens for the specified line.
1320	 * @param lineIndex The line number
1321	 * @param tokenHandler The token handler that will receive the syntax
1322	 * tokens
1323	 * @since jEdit 4.1pre1
1324	 */
1325	public void markTokens(int lineIndex, TokenHandler tokenHandler)
1326	{
1327		Segment seg = new Segment();
1328
1329		if(lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
1330			throw new ArrayIndexOutOfBoundsException(lineIndex);
1331
1332		int firstInvalidLineContext = lineMgr.getFirstInvalidLineContext();
1333		int start;
1334		if(contextInsensitive || firstInvalidLineContext == -1)
1335		{
1336			start = lineIndex;
1337		}
1338		else
1339		{
1340			start = Math.min(firstInvalidLineContext,
1341				lineIndex);
1342		}
1343
1344		if(Debug.TOKEN_MARKER_DEBUG)
1345			Log.log(Log.DEBUG,this,"tokenize from " + start + " to " + lineIndex);
1346		TokenMarker.LineContext oldContext = null;
1347		TokenMarker.LineContext context = null;
1348		for(int i = start; i <= lineIndex; i++)
1349		{
1350			getLineText(i,seg);
1351
1352			oldContext = lineMgr.getLineContext(i);
1353
1354			TokenMarker.LineContext prevContext = (
1355				(i == 0 || contextInsensitive) ? null
1356				: lineMgr.getLineContext(i - 1)
1357			);
1358
1359			context = tokenMarker.markTokens(prevContext,
1360				(i == lineIndex ? tokenHandler
1361				: DummyTokenHandler.INSTANCE), seg);
1362			lineMgr.setLineContext(i,context);
1363		}
1364
1365		int lineCount = lineMgr.getLineCount();
1366		if(lineCount - 1 == lineIndex)
1367			lineMgr.setFirstInvalidLineContext(-1);
1368		else if(oldContext != context)
1369			lineMgr.setFirstInvalidLineContext(lineIndex + 1);
1370		else if(firstInvalidLineContext == -1)
1371			/* do nothing */;
1372		else
1373		{
1374			lineMgr.setFirstInvalidLineContext(Math.max(
1375				firstInvalidLineContext,lineIndex + 1));
1376		}
1377	} //}}}
1378
1379	//{{{ getTokenMarker() method
1380	public TokenMarker getTokenMarker()
1381	{
1382		return tokenMarker;
1383	} //}}}
1384
1385	//{{{ setTokenMarker() method
1386	public void setTokenMarker(TokenMarker tokenMarker)
1387	{
1388		TokenMarker oldTokenMarker = this.tokenMarker;
1389
1390		this.tokenMarker = tokenMarker;
1391
1392		// don't do this on initial token marker
1393		if(oldTokenMarker != null && tokenMarker != oldTokenMarker)
1394		{
1395			lineMgr.setFirstInvalidLineContext(0);
1396		}
1397	} //}}}
1398
1399	//{{{ createPosition() method
1400	/**
1401	 * Creates a floating position.
1402	 * @param offset The offset
1403	 */
1404	public Position createPosition(int offset)
1405	{
1406		try
1407		{
1408			readLock();
1409
1410			if(offset < 0 || offset > contentMgr.getLength())
1411				throw new ArrayIndexOutOfBoundsException(offset);
1412
1413			return positionMgr.createPosition(offset);
1414		}
1415		finally
1416		{
1417			readUnlock();
1418		}
1419	} //}}}
1420
1421	//}}}
1422
1423	//{{{ Property methods
1424
1425	//{{{ propertiesChanged() method
1426	/**
1427	 * Reloads settings from the properties. This should be called
1428	 * after the <code>syntax</code> or <code>folding</code>
1429	 * buffer-local properties are changed.
1430	 */
1431	public void propertiesChanged()
1432	{
1433		String folding = getStringProperty("folding");
1434		FoldHandler handler = FoldHandler.getFoldHandler(folding);
1435
1436		if(handler != null)
1437		{
1438			setFoldHandler(handler);
1439		}
1440		else
1441		{
1442			setFoldHandler(new DummyFoldHandler());
1443		}
1444	} //}}}
1445
1446	//{{{ getTabSize() method
1447	/**
1448	 * Returns the tab size used in this buffer. This is equivalent
1449	 * to calling <code>getProperty("tabSize")</code>.
1450	 * This method is thread-safe.
1451	 */
1452	public int getTabSize()
1453	{
1454		int tabSize = getIntegerProperty("tabSize",8);
1455		if(tabSize <= 0)
1456			return 8;
1457		else
1458			return tabSize;
1459	} //}}}
1460
1461	//{{{ getIndentSize() method
1462	/**
1463	 * Returns the indent size used in this buffer. This is equivalent
1464	 * to calling <code>getProperty("indentSize")</code>.
1465	 * This method is thread-safe.
1466	 * @since jEdit 2.7pre1
1467	 */
1468	public int getIndentSize()
1469	{
1470		int indentSize = getIntegerProperty("indentSize",8);
1471		if(indentSize <= 0)
1472			return 8;
1473		else
1474			return indentSize;
1475	} //}}}
1476
1477	//{{{ getProperty() method
1478	/**
1479	 * Returns the value of a buffer-local property.<p>
1480	 *
1481	 * Using this method is generally discouraged, because it returns an
1482	 * <code>Object</code> which must be cast to another type
1483	 * in order to be useful, and this can cause problems if the object
1484	 * is of a different type than what the caller expects.<p>
1485	 *
1486	 * The following methods should be used instead:
1487	 * <ul>
1488	 * <li>{@link #getStringProperty(String)}</li>
1489	 * <li>{@link #getBooleanProperty(String)}</li>
1490	 * <li>{@link #getIntegerProperty(String,int)}</li>
1491	 * </ul>
1492	 *
1493	 * This method is thread-safe.
1494	 *
1495	 * @param name The property name. For backwards compatibility, this
1496	 * is an <code>Object</code>, not a <code>String</code>.
1497	 */
1498	public Object getProperty(Object name)
1499	{
1500		synchronized(propertyLock)
1501		{
1502			// First try the buffer-local properties
1503			PropValue o = properties.get(name);
1504			if(o != null)
1505				return o.value;
1506
1507			// For backwards compatibility
1508			if(!(name instanceof String))
1509				return null;
1510
1511			Object retVal = getDefaultProperty((String)name);
1512
1513			if(retVal == null)
1514				return null;
1515			else
1516			{
1517				properties.put(name,new PropValue(retVal,true));
1518				return retVal;
1519			}
1520		}
1521	} //}}}
1522
1523	//{{{ getDefaultProperty() method
1524	public Object getDefaultProperty(String key)
1525	{
1526		return null;
1527	} //}}}
1528
1529	//{{{ setProperty() method
1530	/**
1531	 * Sets the value of a buffer-local property.
1532	 * @param name The property name
1533	 * @param value The property value
1534	 * @since jEdit 4.0pre1
1535	 */
1536	public void setProperty(String name, Object value)
1537	{
1538		if(value == null)
1539			properties.remove(name);
1540		else
1541		{
1542			PropValue test = properties.get(name);
1543			if(test == null)
1544				properties.put(name,new PropValue(value,false));
1545			else if(test.value.equals(value))
1546			{
1547				// do nothing
1548			}
1549			else
1550			{
1551				test.value = value;
1552				test.defaultValue = false;
1553			}
1554		}
1555	} //}}}
1556
1557	//{{{ setDefaultProperty() method
1558	public void setDefaultProperty(String name, Object value)
1559	{
1560		properties.put(name,new PropValue(value,true));
1561	} //}}}
1562
1563	//{{{ unsetProperty() method
1564	/**
1565	 * Clears the value of a buffer-local property.
1566	 * @param name The property name
1567	 * @since jEdit 4.0pre1
1568	 */
1569	public void unsetProperty(String name)
1570	{
1571		properties.remove(name);
1572	} //}}}
1573
1574	//{{{ resetCachedProperties() method
1575	public void resetCachedProperties()
1576	{
1577		// Need to reset properties that were cached defaults,
1578		// since the defaults might have changed.
1579		Iterator<PropValue> iter = properties.values().iterator();
1580		while(iter.hasNext())
1581		{
1582			PropValue value = iter.next();
1583			if(value.defaultValue)
1584				iter.remove();
1585		}
1586	} //}}}
1587
1588	//{{{ getStringProperty() method
1589	/**
1590	 * Returns the value of a string property. This method is thread-safe.
1591	 * @param name The property name
1592	 * @since jEdit 4.0pre1
1593	 */
1594	public String getStringProperty(String name)
1595	{
1596		Object obj = getProperty(name);
1597		if(obj != null)
1598			return obj.toString();
1599		else
1600			return null;
1601	} //}}}
1602
1603	//{{{ setStringProperty() method
1604	/**
1605	 * Sets a string property.
1606	 * @param name The property name
1607	 * @param value The value
1608	 * @since jEdit 4.0pre1
1609	 */
1610	public void setStringProperty(String name, String value)
1611	{
1612		setProperty(name,value);
1613	} //}}}
1614
1615	//{{{ getBooleanProperty() methods
1616	/**
1617	 * Returns the value of a boolean property. This method is thread-safe.
1618	 * @param name The property name
1619	 * @since jEdit 4.0pre1
1620	 */
1621	public boolean getBooleanProperty(String name)
1622	{
1623		return getBooleanProperty(name, false);
1624	}
1625
1626	/**
1627	 * Returns the value of a boolean property. This method is thread-safe.
1628	 * @param name The property name
1629	 * @param def The default value
1630	 * @since jEdit 4.3pre17
1631	 */
1632	public boolean getBooleanProperty(String name, boolean def)
1633	{
1634		Object obj = getProperty(name);
1635		return StandardUtilities.getBoolean(obj, def);
1636	} //}}}
1637
1638	//{{{ setBooleanProperty() method
1639	/**
1640	 * Sets a boolean property.
1641	 * @param name The property name
1642	 * @param value The value
1643	 * @since jEdit 4.0pre1
1644	 */
1645	public void setBooleanProperty(String name, boolean value)
1646	{
1647		setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
1648	} //}}}
1649
1650	//{{{ getIntegerProperty() method
1651	/**
1652	 * Returns the value of an integer property. This method is thread-safe.
1653	 * @param name The property name
1654	 * @since jEdit 4.0pre1
1655	 */
1656	public int getIntegerProperty(String name, int defaultValue)
1657	{
1658		boolean defaultValueFlag;
1659		Object obj;
1660		PropValue value = properties.get(name);
1661		if(value != null)
1662		{
1663			obj = value.value;
1664			defaultValueFlag = value.defaultValue;
1665		}
1666		else
1667		{
1668			obj = getProperty(name);
1669			// will be cached from now on...
1670			defaultValueFlag = true;
1671		}
1672
1673		if(obj == null)
1674			return defaultValue;
1675		else if(obj instanceof Number)
1676			return ((Number)obj).intValue();
1677		else
1678		{
1679			try
1680			{
1681				int returnValue = Integer.parseInt(
1682					obj.toString().trim());
1683				properties.put(name,new PropValue(
1684					returnValue,
1685					defaultValueFlag));
1686				return returnValue;
1687			}
1688			catch(Exception e)
1689			{
1690				return defaultValue;
1691			}
1692		}
1693	} //}}}
1694
1695	//{{{ setIntegerProperty() method
1696	/**
1697	 * Sets an integer property.
1698	 * @param name The property name
1699	 * @param value The value
1700	 * @since jEdit 4.0pre1
1701	 */
1702	public void setIntegerProperty(String name, int value)
1703	{
1704		setProperty(name,value);
1705	} //}}}
1706
1707	//{{{ getPatternProperty()
1708	/**
1709	 * Returns the value of a property as a regular expression.
1710	 * This method is thread-safe.
1711	 * @param name The property name
1712	 * @param flags Regular expression compilation flags
1713	 * @since jEdit 4.3pre5
1714	 */
1715	public Pattern getPatternProperty(String name, int flags)
1716	{
1717		synchronized(propertyLock)
1718		{
1719			boolean defaultValueFlag;
1720			Object obj;
1721			PropValue value = properties.get(name);
1722			if(value != null)
1723			{
1724				obj = value.value;
1725				defaultValueFlag = value.defaultValue;
1726			}
1727			else
1728			{
1729				obj = getProperty(name);
1730				// will be cached from now on...
1731				defaultValueFlag = true;
1732			}
1733
1734			if(obj == null)
1735				return null;
1736			else if (obj instanceof Pattern)
1737				return (Pattern) obj;
1738			else
1739			{
1740				Pattern re = Pattern.compile(obj.toString(),flags);
1741				properties.put(name,new PropValue(re,
1742					defaultValueFlag));
1743				return re;
1744			}
1745		}
1746	} //}}}
1747
1748	//{{{ getRuleSetAtOffset() method
1749	/**
1750	 * Returns the syntax highlighting ruleset at the specified offset.
1751	 * @since jEdit 4.1pre1
1752	 */
1753	public ParserRuleSet getRuleSetAtOffset(int offset)
1754	{
1755		int line = getLineOfOffset(offset);
1756		offset -= getLineStartOffset(line);
1757		if(offset != 0)
1758			offset--;
1759
1760		DefaultTokenHandler tokens = new DefaultTokenHandler();
1761		markTokens(line,tokens);
1762		Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
1763		return token.rules;
1764	} //}}}
1765
1766	//{{{ getKeywordMapAtOffset() method
1767	/**
1768	 * Returns the syntax highlighting keyword map in effect at the
1769	 * specified offset. Used by the <b>Complete Word</b> command to
1770	 * complete keywords.
1771	 * @param offset The offset
1772	 * @since jEdit 4.0pre3
1773	 */
1774	public KeywordMap getKeywordMapAtOffset(int offset)
1775	{
1776		return getRuleSetAtOffset(offset).getKeywords();
1777	} //}}}
1778
1779	//{{{ getContextSensitiveProperty() method
1780	/**
1781	 * Some settings, like comment start and end strings, can
1782	 * vary between different parts of a buffer (HTML text and inline
1783	 * JavaScript, for example).
1784	 * @param offset The offset
1785	 * @param name The property name
1786	 * @since jEdit 4.0pre3
1787	 */
1788	public String getContextSensitiveProperty(int offset, String name)
1789	{
1790		ParserRuleSet rules = getRuleSetAtOffset(offset);
1791
1792		Object value = null;
1793
1794		Map<String, String> rulesetProps = rules.getProperties();
1795		if(rulesetProps != null)
1796			value = rulesetProps.get(name);
1797
1798		if(value == null)
1799			return null;
1800		else
1801			return String.valueOf(value);
1802	} //}}}
1803
1804	//{{{ getMode() method
1805	/**
1806	 * Returns this buffer's edit mode. This method is thread-safe.
1807	 */
1808	public Mode getMode()
1809	{
1810		return mode;
1811	} //}}}
1812
1813	//{{{ setMode() methods
1814	/**
1815	 * Sets this buffer's edit mode. Note that calling this before a buffer
1816	 * is loaded will have no effect; in that case, set the "mode" property
1817	 * to the name of the mode. A bit inelegant, I know...
1818	 * @param mode The mode name
1819	 * @since jEdit 4.2pre1
1820	 */
1821	public void setMode(String mode)
1822	{
1823		setMode(ModeProvider.instance.getMode(mode));
1824	}
1825
1826	/**
1827	 * Sets this buffer's edit mode. Note that calling this before a buffer
1828	 * is loaded will have no effect; in that case, set the "mode" property
1829	 * to the name of the mode. A bit inelegant, I know...
1830	 * @param mode The mode
1831	 */
1832	public void setMode(Mode mode)
1833	{
1834		setMode(mode, false);
1835	}
1836
1837	/**
1838	 * Sets this buffer's edit mode. Note that calling this before a buffer
1839	 * is loaded will have no effect; in that case, set the "mode" property
1840	 * to the name of the mode. A bit inelegant, I know...
1841	 * @param mode The mode
1842	 * @param forceContextInsensitive true if you want to force the buffer to be
1843	 * insensitive to the context. Careful it can break syntax highlight. Default
1844	 * value is false
1845	 * @since jEdit 4.5pre1
1846	 */
1847	public void setMode(Mode mode, boolean forceContextInsensitive)
1848	{
1849		/* This protects against stupid people (like me)
1850		 * doing stuff like buffer.setMode(jEdit.getMode(...)); */
1851		if(mode == null)
1852			throw new NullPointerException("Mode must be non-null");
1853
1854		this.mode = mode;
1855
1856		contextInsensitive = forceContextInsensitive ||
1857			mode.getBooleanProperty("contextInsensitive");
1858
1859		setTokenMarker(mode.getTokenMarker());
1860
1861		resetCachedProperties();
1862		propertiesChanged();
1863	}//}}}
1864
1865	//}}}
1866
1867	//{{{ Folding methods
1868
1869	//{{{ isFoldStart() method
1870	/**
1871	 * Returns if the specified line begins a fold.
1872	 * @since jEdit 3.1pre1
1873	 */
1874	public boolean isFoldStart(int line)
1875	{
1876		return line != getLineCount() - 1
1877			&& getFoldLevel(line) < getFoldLevel(line + 1);
1878	} //}}}
1879
1880	//{{{ isFoldEnd() method
1881	/**
1882	 * Returns if the specified line ends a fold.
1883	 * @since jEdit 4.2pre5
1884	 */
1885	public boolean isFoldEnd(int line)
1886	{
1887		return line != getLineCount() - 1
1888			&& getFoldLevel(line) > getFoldLevel(line + 1);
1889	} //}}}
1890
1891	//{{{ invalidateCachedFoldLevels() method
1892	/**
1893	 * Invalidates all cached fold level information.
1894	 * @since jEdit 4.1pre11
1895	 */
1896	public void invalidateCachedFoldLevels()
1897	{
1898		lineMgr.setFirstInvalidFoldLevel(0);
1899		fireFoldLevelChanged(0,getLineCount());
1900	} //}}}
1901
1902	//{{{ getFoldLevel() method
1903	/**
1904	 * Returns the fold level of the specified line.
1905	 * @param line A physical line index
1906	 * @since jEdit 3.1pre1
1907	 */
1908	public int getFoldLevel(int line)
1909	{
1910		if(line < 0 || line >= lineMgr.getLineCount())
1911			throw new ArrayIndexOutOfBoundsException(line);
1912
1913		if(foldHandler instanceof DummyFoldHandler)
1914			return 0;
1915
1916		int firstInvalidFoldLevel = lineMgr.getFirstInvalidFoldLevel();
1917		if(firstInvalidFoldLevel == -1 || line < firstInvalidFoldLevel)
1918		{
1919			return lineMgr.getFoldLevel(line);
1920		}
1921		else
1922		{
1923			if(Debug.FOLD_DEBUG)
1924				Log.log(Log.DEBUG,this,"Invalid fold levels from " + firstInvalidFoldLevel + " to " + line);
1925
1926			int newFoldLevel = 0;
1927			boolean changed = false;
1928			int firstUpdatedFoldLevel = firstInvalidFoldLevel;
1929
1930			for(int i = firstInvalidFoldLevel; i <= line; i++)
1931			{
1932				Segment seg = new Segment();
1933				newFoldLevel = foldHandler.getFoldLevel(this,i,seg);
1934				if(newFoldLevel != lineMgr.getFoldLevel(i))
1935				{
1936					if(Debug.FOLD_DEBUG)
1937						Log.log(Log.DEBUG,this,i + " fold level changed");
1938					changed = true;
1939					// Update preceding fold levels if necessary
1940					if (i == firstInvalidFoldLevel)
1941					{
1942						List<Integer> precedingFoldLevels =
1943							foldHandler.getPrecedingFoldLevels(
1944								this,i,seg,newFoldLevel);
1945						if (precedingFoldLevels != null)
1946						{
1947							int j = i;
1948							for (Integer foldLevel: precedingFoldLevels)
1949							{
1950								j--;
1951								lineMgr.setFoldLevel(j,foldLevel.intValue());
1952							}
1953							if (j < firstUpdatedFoldLevel)
1954								firstUpdatedFoldLevel = j;
1955						}
1956					}
1957				}
1958				lineMgr.setFoldLevel(i,newFoldLevel);
1959			}
1960
1961			if(line == lineMgr.getLineCount() - 1)
1962				lineMgr.setFirstInvalidFoldLevel(-1);
1963			else
1964				lineMgr.setFirstInvalidFoldLevel(line + 1);
1965
1966			if(changed)
1967			{
1968				if(Debug.FOLD_DEBUG)
1969					Log.log(Log.DEBUG,this,"fold level changed: " + firstUpdatedFoldLevel + ',' + line);
1970				fireFoldLevelChanged(firstUpdatedFoldLevel,line);
1971			}
1972
1973			return newFoldLevel;
1974		}
1975	} //}}}
1976
1977	//{{{ getFoldAtLine() method
1978	/**
1979	 * Returns an array. The first element is the start line, the
1980	 * second element is the end line, of the fold containing the
1981	 * specified line number.
1982	 * @param line The line number
1983	 * @since jEdit 4.0pre3
1984	 */
1985	public int[] getFoldAtLine(int line)
1986	{
1987		int start, end;
1988
1989		if(isFoldStart(line))
1990		{
1991			start = line;
1992			int foldLevel = getFoldLevel(line);
1993
1994			line++;
1995
1996			while(getFoldLevel(line) > foldLevel)
1997			{
1998				line++;
1999
2000				if(line == getLineCount())
2001					break;
2002			}
2003
2004			end = line - 1;
2005		}
2006		else
2007		{
2008			start = line;
2009			int foldLevel = getFoldLevel(line);
2010			while(getFoldLevel(start) >= foldLevel)
2011			{
2012				if(start == 0)
2013					break;
2014				else
2015					start--;
2016			}
2017
2018			end = line;
2019			while(getFoldLevel(end) >= foldLevel)
2020			{
2021				end++;
2022
2023				if(end == getLineCount())
2024					break;
2025			}
2026
2027			end--;
2028		}
2029
2030		while(getLineLength(end) == 0 && end > start)
2031			end--;
2032
2033		return new int[] { start, end };
2034	} //}}}
2035
2036	//{{{ getFoldHandler() method
2037	/**
2038	 * Returns the current buffer's fold handler.
2039	 * @since jEdit 4.2pre1
2040	 */
2041	public FoldHandler getFoldHandler()
2042	{
2043		return foldHandler;
2044	} //}}}
2045
2046	//{{{ setFoldHandler() method
2047	/**
2048	 * Sets the buffer's fold handler.
2049	 * @since jEdit 4.2pre2
2050	 */
2051	public void setFoldHandler(FoldHandler foldHandler)
2052	{
2053		FoldHandler oldFoldHandler = this.foldHandler;
2054
2055		if(foldHandler.equals(oldFoldHandler))
2056			return;
2057
2058		this.foldHandler = foldHandler;
2059
2060		lineMgr.setFirstInvalidFoldLevel(0);
2061
2062		fireFoldHandlerChanged();
2063	} //}}}
2064
2065	//}}}
2066
2067	//{{{ Undo
2068
2069	//{{{ undo() method
2070	/**
2071	 * Undoes the most recent edit.
2072	 *
2073	 * @since jEdit 4.0pre1
2074	 */
2075	public void undo(TextArea textArea)
2076	{
2077		if(undoMgr == null)
2078			return;
2079
2080		if(!isEditable())
2081		{
2082			textArea.getToolkit().beep();
2083			return;
2084		}
2085
2086		try
2087		{
2088			writeLock();
2089
2090			undoInProgress = true;
2091			fireBeginUndo();
2092			int caret = undoMgr.undo();
2093			if(caret == -1)
2094				textArea.getToolkit().beep();
2095			else
2096				textArea.setCaretPosition(caret);
2097
2098			fireEndUndo();
2099			fireTransactionComplete();
2100		}
2101		finally
2102		{
2103			undoInProgress = false;
2104
2105			writeUnlock();
2106		}
2107	} //}}}
2108
2109	//{{{ redo() method
2110	/**
2111	 * Redoes the most recently undone edit.
2112	 *
2113	 * @since jEdit 2.7pre2
2114	 */
2115	public void redo(TextArea textArea)
2116	{
2117		if(undoMgr == null)
2118			return;
2119
2120		if(!isEditable())
2121		{
2122			Toolkit.getDefaultToolkit().beep();
2123			return;
2124		}
2125
2126		try
2127		{
2128			writeLock();
2129
2130			undoInProgress = true;
2131			fireBeginRedo();
2132			int caret = undoMgr.redo();
2133			if(caret == -1)
2134				textArea.getToolkit().beep();
2135			else
2136				textArea.setCaretPosition(caret);
2137
2138			fireEndRedo();
2139			fireTransactionComplete();
2140		}
2141		finally
2142		{
2143			undoInProgress = false;
2144
2145			writeUnlock();
2146		}
2147	} //}}}
2148
2149	//{{{ isTransactionInProgress() method
2150	/**
2151	 * Returns if an undo or compound edit is currently in progress. If this
2152	 * method returns true, then eventually a
2153	 * {@link org.gjt.sp.jedit.buffer.BufferListener#transactionComplete(JEditBuffer)}
2154	 * buffer event will get fired.
2155	 * @since jEdit 4.0pre6
2156	 */
2157	public boolean isTransactionInProgress()
2158	{
2159		return transaction || undoInProgress || insideCompoundEdit() || loading;
2160	} //}}}
2161
2162	//{{{ beginCompoundEdit() method
2163	/**
2164	 * Starts a compound edit. All edits from now on until
2165	 * {@link #endCompoundEdit()} are called will be merged
2166	 * into one. This can be used to make a complex operation
2167	 * undoable in one step. Nested calls to
2168	 * {@link #beginCompoundEdit()}

Large files files are truncated, but you can click here to view the full file