PageRenderTime 164ms CodeModel.GetById 54ms app.highlight 92ms RepoModel.GetById 1ms app.codeStats 1ms

/jEdit/tags/jedit-4-1-pre5/org/gjt/sp/jedit/Buffer.java

#
Java | 2986 lines | 1721 code | 377 blank | 888 comment | 362 complexity | ab41d57e7f11db1b29666860e549e327 MD5 | raw file

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

   1/*
   2 * Buffer.java - jEdit buffer
   3 * :tabSize=8:indentSize=8:noTabs=false:
   4 * :folding=explicit:collapseFolds=1:
   5 *
   6 * Copyright (C) 1998, 1999, 2000, 2001, 2002 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;
  25
  26//{{{ Imports
  27import gnu.regexp.*;
  28import javax.swing.*;
  29import javax.swing.event.*;
  30import javax.swing.text.*;
  31import java.awt.Toolkit;
  32import java.io.File;
  33import java.util.*;
  34import org.gjt.sp.jedit.browser.VFSBrowser;
  35import org.gjt.sp.jedit.buffer.*;
  36import org.gjt.sp.jedit.io.*;
  37import org.gjt.sp.jedit.msg.*;
  38import org.gjt.sp.jedit.search.RESearchMatcher;
  39import org.gjt.sp.jedit.syntax.*;
  40import org.gjt.sp.jedit.textarea.*;
  41import org.gjt.sp.util.*;
  42//}}}
  43
  44/**
  45 * An in-memory copy of an open file.
  46 *
  47 * This class is partially thread-safe, however you must pay attention to a few
  48 * very important issues:
  49 * <ul>
  50 * <li>Changes to a buffer can only be made from the AWT thread.
  51 * <li>When accessing the buffer from another thread, you must
  52 * grab a read lock if you plan on performing more than one call, to ensure that
  53 * the buffer contents are not changed by the AWT thread for the duration of the
  54 * lock.
  55 * <li>
  56 *
  57 * @author Slava Pestov
  58 * @version $Id: Buffer.java 4342 2002-09-20 19:53:23Z spestov $
  59 */
  60public class Buffer implements EBComponent
  61{
  62	//{{{ Some constants
  63	/**
  64	 * Line separator property.
  65	 */
  66	public static final String LINESEP = "lineSeparator";
  67
  68	/**
  69	 * Backed up property.
  70	 * @since jEdit 3.2pre2
  71	 */
  72	public static final String BACKED_UP = "Buffer__backedUp";
  73
  74	/**
  75	 * Caret info properties.
  76	 * @since jEdit 3.2pre1
  77	 */
  78	public static final String CARET = "Buffer__caret";
  79	public static final String SELECTION = "Buffer__selection";
  80
  81	/**
  82	 * This should be a physical line number, so that the scroll
  83	 * position is preserved correctly across reloads (which will
  84	 * affect virtual line numbers, due to fold being reset)
  85	 */
  86	public static final String SCROLL_VERT = "Buffer__scrollVert";
  87	public static final String SCROLL_HORIZ = "Buffer__scrollHoriz";
  88
  89	/**
  90	 * Character encoding used when loading and saving.
  91	 * @since jEdit 3.2pre4
  92	 */
  93	public static final String ENCODING = "encoding";
  94
  95	/**
  96	 * This property is set to 'true' if the file has a trailing newline.
  97	 * @since jEdit 4.0pre1
  98	 */
  99	public static final String TRAILING_EOL = "trailingEOL";
 100
 101	/**
 102	 * This property is set to 'true' if the file should be GZipped.
 103	 * @since jEdit 4.0pre4
 104	 */
 105	public static final String GZIPPED = "gzipped";
 106	//}}}
 107
 108	//{{{ Input/output methods
 109
 110	//{{{ showInsertFileDialog() method
 111	/**
 112	 * Displays the 'insert file' dialog box and inserts the selected file
 113	 * into the buffer.
 114	 * @param view The view
 115	 * @since jEdit 2.7pre2
 116	 */
 117	public void showInsertFileDialog(View view)
 118	{
 119		String[] files = GUIUtilities.showVFSFileDialog(view,null,
 120			VFSBrowser.OPEN_DIALOG,false);
 121
 122		if(files != null)
 123			insertFile(view,files[0]);
 124	} //}}}
 125
 126	//{{{ reload() method
 127	/**
 128	 * Reloads the buffer from disk, asking for confirmation if the buffer
 129	 * is dirty.
 130	 * @param view The view
 131	 * @since jEdit 2.7pre2
 132	 */
 133	public void reload(View view)
 134	{
 135		if(getFlag(DIRTY))
 136		{
 137			String[] args = { name };
 138			int result = GUIUtilities.confirm(view,"changedreload",
 139				args,JOptionPane.YES_NO_OPTION,
 140				JOptionPane.WARNING_MESSAGE);
 141			if(result != JOptionPane.YES_OPTION)
 142				return;
 143		}
 144
 145		view.getEditPane().saveCaretInfo();
 146		load(view,true);
 147	} //}}}
 148
 149	//{{{ load() method
 150	/**
 151	 * Loads the buffer from disk, even if it is loaded already.
 152	 * @param view The view
 153	 * @param reload If true, user will not be asked to recover autosave
 154	 * file, if any
 155	 *
 156	 * @since 2.5pre1
 157	 */
 158	public boolean load(final View view, final boolean reload)
 159	{
 160		if(isPerformingIO())
 161		{
 162			GUIUtilities.error(view,"buffer-multiple-io",null);
 163			return false;
 164		}
 165
 166		setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
 167
 168		setFlag(LOADING,true);
 169
 170		// view text areas temporarily blank out while a buffer is
 171		// being loaded, to indicate to the user that there is no
 172		// data available yet.
 173		if(!getFlag(TEMPORARY))
 174			EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED));
 175
 176		final boolean loadAutosave;
 177
 178		if(reload || !getFlag(NEW_FILE))
 179		{
 180			if(file != null)
 181				modTime = file.lastModified();
 182
 183			// Only on initial load
 184			if(!reload && autosaveFile != null && autosaveFile.exists())
 185				loadAutosave = recoverAutosave(view);
 186			else
 187			{
 188				if(autosaveFile != null)
 189					autosaveFile.delete();
 190				loadAutosave = false;
 191			}
 192
 193			if(!loadAutosave)
 194			{
 195				// this returns false if initial sanity
 196				// checks (if the file is a directory, etc)
 197				// fail
 198				if(!vfs.load(view,this,path))
 199				{
 200					setFlag(LOADING,false);
 201					return false;
 202				}
 203			}
 204		}
 205		else
 206			loadAutosave = false;
 207
 208		//{{{ Do some stuff once loading is finished
 209		Runnable runnable = new Runnable()
 210		{
 211			public void run()
 212			{
 213				String newPath = getStringProperty(
 214					BufferIORequest.NEW_PATH);
 215				Segment seg = (Segment)getProperty(
 216					BufferIORequest.LOAD_DATA);
 217				IntegerArray endOffsets = (IntegerArray)
 218					getProperty(BufferIORequest.END_OFFSETS);
 219
 220				// below remove() call only works if read only
 221				// is false. this is a slightly silly workaround.
 222				boolean readOnly = isReadOnly();
 223				setFlag(READ_ONLY,false);
 224
 225				// For `reload' command
 226				remove(0,getLength());
 227
 228				if(seg != null && endOffsets != null)
 229				{
 230					// This is faster than Buffer.insert()
 231					try
 232					{
 233						writeLock();
 234
 235						// theoretically a segment could
 236						// have seg.offset != 0 but
 237						// SegmentBuffer never does that
 238						contentMgr._setContent(seg.array,seg.count);
 239
 240						contentInserted(0,seg.count,endOffsets);
 241					}
 242					catch(OutOfMemoryError oom)
 243					{
 244						Log.log(Log.ERROR,this,oom);
 245						VFSManager.error(view,path,"out-of-memory-error",null);
 246					}
 247					finally
 248					{
 249						writeUnlock();
 250					}
 251				}
 252
 253				setFlag(READ_ONLY,readOnly);
 254
 255				unsetProperty(BufferIORequest.LOAD_DATA);
 256				unsetProperty(BufferIORequest.END_OFFSETS);
 257				unsetProperty(BufferIORequest.NEW_PATH);
 258
 259				undoMgr.clear();
 260				undoMgr.setLimit(jEdit.getIntegerProperty(
 261					"buffer.undoCount",100));
 262
 263				if(!getFlag(TEMPORARY))
 264					finishLoading();
 265
 266				/* Ultra-obscure: we have to fire this event
 267				 * after the buffer might have been collapsed
 268				 * by finishLoading(), since finishLoading(),
 269				 * unlike "official" fold APIs, does not notify
 270				 * the text area to invalidate its cached
 271				 * virtual to physical information. Note that
 272				 * the text area's contentInserted() handler
 273				 * updates 'lastPhysLine' even if the LOADING
 274				 * flag is set. */
 275				fireContentInserted(0,0,getLineCount(),getLength() - 1);
 276
 277				setFlag(LOADING,false);
 278
 279				// if reloading a file, clear dirty flag
 280				if(reload)
 281					setDirty(false);
 282
 283				if(!loadAutosave && newPath != null && !path.equals(newPath))
 284					setPath(newPath);
 285
 286				// if loadAutosave is false, we loaded an
 287				// autosave file, so we set 'dirty' to true
 288
 289				// note that we don't use setDirty(),
 290				// because a) that would send an unnecessary
 291				// message, b) it would also set the
 292				// AUTOSAVE_DIRTY flag, which will make
 293				// the autosave thread write out a
 294				// redundant autosave file
 295				if(loadAutosave)
 296					setFlag(DIRTY,true);
 297
 298				// send some EditBus messages
 299				if(!getFlag(TEMPORARY))
 300				{
 301					EditBus.send(new BufferUpdate(Buffer.this,
 302						view,BufferUpdate.LOADED));
 303					//EditBus.send(new BufferUpdate(Buffer.this,
 304					//	view,BufferUpdate.MARKERS_CHANGED));
 305				}
 306			}
 307		}; //}}}
 308
 309		if(getFlag(TEMPORARY))
 310			runnable.run();
 311		else
 312			VFSManager.runInAWTThread(runnable);
 313
 314		return true;
 315	} //}}}
 316
 317	//{{{ insertFile() method
 318	/**
 319	 * Loads a file from disk, and inserts it into this buffer.
 320	 * @param view The view
 321	 *
 322	 * @since 4.0pre1
 323	 */
 324	public boolean insertFile(final View view, String path)
 325	{
 326		if(isPerformingIO())
 327		{
 328			GUIUtilities.error(view,"buffer-multiple-io",null);
 329			return false;
 330		}
 331
 332		setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
 333
 334		path = MiscUtilities.constructPath(this.path,path);
 335
 336		Buffer buffer = jEdit.getBuffer(path);
 337		if(buffer != null)
 338		{
 339			view.getTextArea().setSelectedText(
 340				buffer.getText(0,buffer.getLength()));
 341			return true;
 342		}
 343
 344		VFS vfs = VFSManager.getVFSForPath(path);
 345
 346		setFlag(IO,true);
 347
 348		// this returns false if initial sanity
 349		// checks (if the file is a directory, etc)
 350		// fail
 351		if(!vfs.insert(view,this,path))
 352		{
 353			setFlag(IO,false);
 354			return false;
 355		}
 356
 357		// Do some stuff once loading is finished
 358		VFSManager.runInAWTThread(new Runnable()
 359		{
 360			public void run()
 361			{
 362				setFlag(IO,false);
 363
 364				SegmentBuffer sbuf = (SegmentBuffer)getProperty(
 365					BufferIORequest.LOAD_DATA);
 366				if(sbuf != null)
 367				{
 368					unsetProperty(BufferIORequest.LOAD_DATA);
 369
 370					view.getTextArea().setSelectedText(sbuf.toString());
 371				}
 372			}
 373		});
 374
 375		return true;
 376	} //}}}
 377
 378	//{{{ autosave() method
 379	/**
 380	 * Autosaves this buffer.
 381	 */
 382	public void autosave()
 383	{
 384		if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY)
 385			|| !getFlag(DIRTY)
 386			|| getFlag(LOADING)
 387			|| getFlag(IO))
 388			return;
 389
 390		setFlag(AUTOSAVE_DIRTY,false);
 391
 392		VFSManager.runInWorkThread(new BufferIORequest(
 393			BufferIORequest.AUTOSAVE,null,this,null,
 394			VFSManager.getFileVFS(),autosaveFile.getPath()));
 395	} //}}}
 396
 397	//{{{ saveAs() method
 398	/**
 399	 * Prompts the user for a file to save this buffer to.
 400	 * @param view The view
 401	 * @param rename True if the buffer's path should be changed, false
 402	 * if only a copy should be saved to the specified filename
 403	 * @since jEdit 2.6pre5
 404	 */
 405	public boolean saveAs(View view, boolean rename)
 406	{
 407		String[] files = GUIUtilities.showVFSFileDialog(view,path,
 408			VFSBrowser.SAVE_DIALOG,false);
 409
 410		// files[] should have length 1, since the dialog type is
 411		// SAVE_DIALOG
 412		if(files == null)
 413			return false;
 414
 415		return save(view,files[0],rename);
 416	} //}}}
 417
 418	//{{{ save() method
 419	/**
 420	 * Saves this buffer to the specified path name, or the current path
 421	 * name if it's null.
 422	 * @param view The view
 423	 * @param path The path name to save the buffer to, or null to use
 424	 * the existing path
 425	 */
 426	public boolean save(View view, String path)
 427	{
 428		return save(view,path,true);
 429	} //}}}
 430
 431	//{{{ save() method
 432	/**
 433	 * Saves this buffer to the specified path name, or the current path
 434	 * name if it's null.
 435	 * @param view The view
 436	 * @param path The path name to save the buffer to, or null to use
 437	 * the existing path
 438	 * @param rename True if the buffer's path should be changed, false
 439	 * if only a copy should be saved to the specified filename
 440	 * @since jEdit 2.6pre5
 441	 */
 442	public boolean save(final View view, String path, final boolean rename)
 443	{
 444		if(isPerformingIO())
 445		{
 446			GUIUtilities.error(view,"buffer-multiple-io",null);
 447			return false;
 448		}
 449
 450		setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
 451
 452		if(path == null && getFlag(NEW_FILE))
 453			return saveAs(view,rename);
 454
 455		if(path == null && file != null)
 456		{
 457			long newModTime = file.lastModified();
 458
 459			if(newModTime != modTime
 460				&& jEdit.getBooleanProperty("view.checkModStatus"))
 461			{
 462				Object[] args = { this.path };
 463				int result = GUIUtilities.confirm(view,
 464					"filechanged-save",args,
 465					JOptionPane.YES_NO_OPTION,
 466					JOptionPane.WARNING_MESSAGE);
 467				if(result != JOptionPane.YES_OPTION)
 468					return false;
 469			}
 470		}
 471
 472		setFlag(IO,true);
 473		EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING));
 474
 475		final String oldPath = this.path;
 476		final String newPath = (path == null ? this.path : path);
 477
 478		VFS vfs = VFSManager.getVFSForPath(newPath);
 479
 480		if(!vfs.save(view,this,newPath))
 481		{
 482			setFlag(IO,false);
 483			return false;
 484		}
 485
 486		// Once save is complete, do a few other things
 487		VFSManager.runInAWTThread(new Runnable()
 488		{
 489			public void run()
 490			{
 491				setFlag(IO,false);
 492				finishSaving(view,oldPath,newPath,rename,
 493					getBooleanProperty(BufferIORequest
 494					.ERROR_OCCURRED));
 495			}
 496		});
 497
 498		return true;
 499	} //}}}
 500
 501	//{{{ checkFileStatus() method
 502	public static final int FILE_NOT_CHANGED = 0;
 503	public static final int FILE_CHANGED = 1;
 504	public static final int FILE_DELETED = 2;
 505	/**
 506	 * Check if the buffer has changed on disk.
 507	 * @return One of <code>NOT_CHANGED</code>, <code>CHANGED</code>, or
 508	 * <code>DELETED</code>.
 509	 *
 510	 * @since jEdit 4.1pre3
 511	 */
 512	public int checkFileStatus()
 513	{
 514		// - don't do these checks while a save is in progress,
 515		// because for a moment newModTime will be greater than
 516		// oldModTime, due to the multithreading
 517		// - only supported on local file system
 518		if(!getFlag(IO) && !getFlag(LOADING) && file != null)
 519		{
 520			boolean newReadOnly = (file.exists() && !file.canWrite());
 521			if(newReadOnly != getFlag(READ_ONLY))
 522			{
 523				setFlag(READ_ONLY,newReadOnly);
 524				EditBus.send(new BufferUpdate(this,null,
 525					BufferUpdate.DIRTY_CHANGED));
 526			}
 527
 528			long oldModTime = modTime;
 529			long newModTime = file.lastModified();
 530
 531			if(newModTime != oldModTime)
 532			{
 533				modTime = newModTime;
 534
 535				if(!file.exists())
 536				{
 537					setFlag(NEW_FILE,true);
 538					EditBus.send(new BufferUpdate(this,null,
 539						BufferUpdate.DIRTY_CHANGED));
 540					return FILE_DELETED;
 541				}
 542				else
 543					return FILE_CHANGED;
 544			}
 545		}
 546
 547		return FILE_NOT_CHANGED;
 548	} //}}}
 549
 550	//{{{ checkModTime() method
 551	/**
 552	 * Check if the buffer has changed on disk.
 553	 */
 554	public void checkModTime(View view)
 555	{
 556		int status = checkFileStatus();
 557
 558		// still need to call the status check even if this option is off,
 559		// so that the write protection is updated if it changes on disk
 560		if(!jEdit.getBooleanProperty("view.checkModStatus"))
 561			return;
 562
 563		if(status == FILE_DELETED)
 564		{
 565			Object[] args = { path };
 566			GUIUtilities.message(view,"filedeleted",args);
 567		}
 568		else if(status == FILE_CHANGED)
 569		{
 570			String prop = (isDirty() ? "filechanged-dirty"
 571				: "filechanged-focus");
 572
 573			Object[] args = { path };
 574			int result = GUIUtilities.confirm(view,
 575				prop,args,JOptionPane.YES_NO_OPTION,
 576				JOptionPane.WARNING_MESSAGE);
 577			if(result == JOptionPane.YES_OPTION)
 578			{
 579				view.getEditPane().saveCaretInfo();
 580				load(view,true);
 581			}
 582		}
 583	} //}}}
 584
 585	//}}}
 586
 587	//{{{ Getters/setter methods for various buffer meta-data
 588
 589	//{{{ getLastModified() method
 590	/**
 591	 * Returns the last time jEdit modified the file on disk.
 592	 */
 593	public long getLastModified()
 594	{
 595		return modTime;
 596	} //}}}
 597
 598	//{{{ setLastModified() method
 599	/**
 600	 * Sets the last time jEdit modified the file on disk.
 601	 * @param modTime The new modification time
 602	 */
 603	public void setLastModified(long modTime)
 604	{
 605		this.modTime = modTime;
 606	} //}}}
 607
 608	//{{{ getVFS() method
 609	/**
 610	 * Returns the virtual filesystem responsible for loading and
 611	 * saving this buffer.
 612	 */
 613	public VFS getVFS()
 614	{
 615		return vfs;
 616	} //}}}
 617
 618	//{{{ getAutosaveFile() method
 619	/**
 620	 * Returns the autosave file for this buffer. This may be null if
 621	 * the file is non-local.
 622	 */
 623	public final File getAutosaveFile()
 624	{
 625		return autosaveFile;
 626	} //}}}
 627
 628	//{{{ getName() method
 629	/**
 630	 * Returns the name of this buffer.
 631	 */
 632	public final String getName()
 633	{
 634		return name;
 635	} //}}}
 636
 637	//{{{ getPath() method
 638	/**
 639	 * Returns the path name of this buffer.
 640	 */
 641	public final String getPath()
 642	{
 643		return path;
 644	} //}}}
 645
 646	//{{{ isClosed() method
 647	/**
 648	 * Returns true if this buffer has been closed with
 649	 * <code>jEdit.closeBuffer()</code>.
 650	 */
 651	public final boolean isClosed()
 652	{
 653		return getFlag(CLOSED);
 654	} //}}}
 655
 656	//{{{ isLoaded() method
 657	/**
 658	 * Returns true if the buffer is loaded.
 659	 */
 660	public final boolean isLoaded()
 661	{
 662		return !getFlag(LOADING);
 663	} //}}}
 664
 665	//{{{ isPerformingIO() method
 666	/**
 667	 * Returns true if the buffer is currently performing I/O.
 668	 * @since jEdit 2.7pre1
 669	 */
 670	public final boolean isPerformingIO()
 671	{
 672		return getFlag(LOADING) || getFlag(IO);
 673	} //}}}
 674
 675	//{{{ isNewFile() method
 676	/**
 677	 * Returns true if this file doesn't exist on disk.
 678	 */
 679	public final boolean isNewFile()
 680	{
 681		return getFlag(NEW_FILE);
 682	} //}}}
 683
 684	//{{{ setNewFile() method
 685	/**
 686	 * Sets the new file flag.
 687	 * @param newFile The new file flag
 688	 */
 689	public final void setNewFile(boolean newFile)
 690	{
 691		setFlag(NEW_FILE,newFile);
 692		if(!newFile)
 693			setFlag(UNTITLED,false);
 694	} //}}}
 695
 696	//{{{ isUntitled() method
 697	/**
 698	 * Returns true if this file is 'untitled'.
 699	 */
 700	public final boolean isUntitled()
 701	{
 702		return getFlag(UNTITLED);
 703	} //}}}
 704
 705	//{{{ isDirty() method
 706	/**
 707	 * Returns true if this file has changed since last save, false
 708	 * otherwise.
 709	 */
 710	public final boolean isDirty()
 711	{
 712		return getFlag(DIRTY);
 713	} //}}}
 714
 715	//{{{ isReadOnly() method
 716	/**
 717	 * Returns true if this file is read only, false otherwise.
 718	 */
 719	public final boolean isReadOnly()
 720	{
 721		return getFlag(READ_ONLY);
 722	} //}}}
 723
 724	//{{{ isEditable() method
 725	/**
 726	 * Returns true if this file is editable, false otherwise.
 727	 * @since jEdit 2.7pre1
 728	 */
 729	public final boolean isEditable()
 730	{
 731		return !(getFlag(READ_ONLY) || getFlag(IO) || getFlag(LOADING));
 732	} //}}}
 733
 734	//{{{ isReadOnly() method
 735	/**
 736	 * Sets the read only flag.
 737	 * @param readOnly The read only flag
 738	 */
 739	public final void setReadOnly(boolean readOnly)
 740	{
 741		setFlag(READ_ONLY,readOnly);
 742	} //}}}
 743
 744	//{{{ setDirty() method
 745	/**
 746	 * Sets the `dirty' (changed since last save) flag of this buffer.
 747	 */
 748	public void setDirty(boolean d)
 749	{
 750		boolean old_d = getFlag(DIRTY);
 751
 752		if(d)
 753		{
 754			if(getFlag(LOADING) || getFlag(READ_ONLY))
 755				return;
 756			if(getFlag(DIRTY) && getFlag(AUTOSAVE_DIRTY))
 757				return;
 758			setFlag(DIRTY,true);
 759			setFlag(AUTOSAVE_DIRTY,true);
 760		}
 761		else
 762		{
 763			setFlag(DIRTY,false);
 764			setFlag(AUTOSAVE_DIRTY,false);
 765
 766			// this ensures that undo can clear the dirty flag properly
 767			// when all edits up to a save are undone
 768			undoMgr.bufferSaved();
 769		}
 770
 771		if(d != old_d)
 772		{
 773			EditBus.send(new BufferUpdate(this,null,
 774				BufferUpdate.DIRTY_CHANGED));
 775		}
 776	} //}}}
 777
 778	//{{{ isTemporary() method
 779	/**
 780	 * Returns if this is a temporary buffer.
 781	 * @see jEdit#openTemporary(View,String,String,boolean)
 782	 * @see jEdit#commitTemporary(Buffer)
 783	 * @since jEdit 2.2pre7
 784	 */
 785	public boolean isTemporary()
 786	{
 787		return getFlag(TEMPORARY);
 788	} //}}}
 789
 790	//{{{ getIcon() method
 791	/**
 792	 * Returns this buffer's icon.
 793	 * @since jEdit 2.6pre6
 794	 */
 795	public Icon getIcon()
 796	{
 797		if(getFlag(DIRTY))
 798			return GUIUtilities.DIRTY_BUFFER_ICON;
 799		else if(getFlag(READ_ONLY))
 800			return GUIUtilities.READ_ONLY_BUFFER_ICON;
 801		else if(getFlag(NEW_FILE))
 802			return GUIUtilities.NEW_BUFFER_ICON;
 803		else
 804			return GUIUtilities.NORMAL_BUFFER_ICON;
 805	} //}}}
 806
 807	//}}}
 808
 809	//{{{ Thread safety
 810
 811	//{{{ readLock() method
 812	/**
 813	 * The buffer is guaranteed not to change between calls to
 814	 * <code>readLock()</code> and <code>readUnlock()</code>.
 815	 */
 816	public final void readLock()
 817	{
 818		lock.readLock();
 819	} //}}}
 820
 821	//{{{ readUnlock() method
 822	/**
 823	 * The buffer is guaranteed not to change between calls to
 824	 * <code>readLock()</code> and <code>readUnlock()</code>.
 825	 */
 826	public final void readUnlock()
 827	{
 828		lock.readUnlock();
 829	} //}}}
 830
 831	//{{{ writeLock() method
 832	/**
 833	 * The buffer cintents are guaranteed not to be read or written
 834	 * by other threads between calls to <code>writeLock()</code>
 835	 * and <code>writeUnlock()</code>.
 836	 */
 837	public final void writeLock()
 838	{
 839		lock.writeLock();
 840	} //}}}
 841
 842	//{{{ writeUnlock() method
 843	/**
 844	 * The buffer cintents are guaranteed not to be read or written
 845	 * by other threads between calls to <code>writeLock()</code>
 846	 * and <code>writeUnlock()</code>.
 847	 */
 848	public final void writeUnlock()
 849	{
 850		lock.writeUnlock();
 851	} //}}}
 852
 853	//}}}
 854
 855	//{{{ Line offset methods
 856
 857	//{{{ getLength() method
 858	/**
 859	 * Returns the number of characters in the buffer.
 860	 */
 861	public int getLength()
 862	{
 863		// no need to lock since this just returns a value and that's it
 864		return contentMgr.getLength();
 865	} //}}}
 866
 867	//{{{ getLineCount() method
 868	/**
 869	 * Returns the number of physical lines in the buffer.
 870	 * This method is thread-safe.
 871	 * @since jEdit 3.1pre1
 872	 */
 873	public int getLineCount()
 874	{
 875		// no need to lock since this just returns a value and that's it
 876		return offsetMgr.getLineCount();
 877	} //}}}
 878
 879	//{{{ getLineOfOffset() method
 880	/**
 881	 * Returns the line containing the specified offset.
 882	 * This method is thread-safe.
 883	 * @param offset The offset
 884	 * @since jEdit 4.0pre1
 885	 */
 886	public final int getLineOfOffset(int offset)
 887	{
 888		try
 889		{
 890			readLock();
 891
 892			if(offset < 0 || offset > getLength())
 893				throw new ArrayIndexOutOfBoundsException(offset);
 894
 895			return offsetMgr.getLineOfOffset(offset);
 896		}
 897		finally
 898		{
 899			readUnlock();
 900		}
 901	} //}}}
 902
 903	//{{{ getLineStartOffset() method
 904	/**
 905	 * Returns the start offset of the specified line.
 906	 * This method is thread-safe.
 907	 * @param line The line
 908	 * @return The start offset of the specified line
 909	 * @since jEdit 4.0pre1
 910	 */
 911	public int getLineStartOffset(int line)
 912	{
 913		try
 914		{
 915			readLock();
 916
 917			if(line < 0 || line >= offsetMgr.getLineCount())
 918				throw new ArrayIndexOutOfBoundsException(line);
 919			else if(line == 0)
 920				return 0;
 921
 922			return offsetMgr.getLineEndOffset(line - 1);
 923		}
 924		finally
 925		{
 926			readUnlock();
 927		}
 928	} //}}}
 929
 930	//{{{ getLineEndOffset() method
 931	/**
 932	 * Returns the end offset of the specified line.
 933	 * This method is thread-safe.
 934	 * @param line The line
 935	 * @return The end offset of the specified line
 936	 * invalid.
 937	 * @since jEdit 4.0pre1
 938	 */
 939	public int getLineEndOffset(int line)
 940	{
 941		try
 942		{
 943			readLock();
 944
 945			if(line < 0 || line >= offsetMgr.getLineCount())
 946				throw new ArrayIndexOutOfBoundsException(line);
 947
 948			return offsetMgr.getLineEndOffset(line);
 949		}
 950		finally
 951		{
 952			readUnlock();
 953		}
 954	} //}}}
 955
 956	//{{{ getLineLength() method
 957	/**
 958	 * Returns the length of the specified line.
 959	 * This method is thread-safe.
 960	 * @param line The line
 961	 * @since jEdit 4.0pre1
 962	 */
 963	public int getLineLength(int line)
 964	{
 965		try
 966		{
 967			readLock();
 968
 969			return getLineEndOffset(line)
 970				- getLineStartOffset(line) - 1;
 971		}
 972		finally
 973		{
 974			readUnlock();
 975		}
 976	} //}}}
 977
 978	//}}}
 979
 980	//{{{ Text getters are setters
 981
 982	//{{{ getLineText() method
 983	/**
 984	 * Returns the text on the specified line.
 985	 * This method is thread-safe.
 986	 * @param lineIndex The line
 987	 * @return The text, or null if the line is invalid
 988	 * @since jEdit 4.0pre1
 989	 */
 990	public String getLineText(int lineIndex)
 991	{
 992		try
 993		{
 994			readLock();
 995
 996			return getText(getLineStartOffset(lineIndex),
 997				getLineLength(lineIndex));
 998		}
 999		finally
1000		{
1001			readUnlock();
1002		}
1003	} //}}}
1004
1005	//{{{ getLineText() method
1006	/**
1007	 * Copies the text on the specified line into a segment.
1008	 * This method is thread-safe.
1009	 * @param lineIndex The line
1010	 * @since jEdit 4.0pre1
1011	 */
1012	public void getLineText(int lineIndex, Segment segment)
1013	{
1014		try
1015		{
1016			readLock();
1017
1018			getText(getLineStartOffset(lineIndex),
1019				getLineLength(lineIndex),segment);
1020		}
1021		finally
1022		{
1023			readUnlock();
1024		}
1025	} //}}}
1026
1027	//{{{ getText() method
1028	/**
1029	 * Returns the specified text range.
1030	 * @param start The start offset
1031	 * @param length The number of characters to get
1032	 */
1033	public String getText(int start, int length)
1034	{
1035		try
1036		{
1037			readLock();
1038
1039			if(start < 0 || length < 0
1040				|| start + length > contentMgr.getLength())
1041				throw new ArrayIndexOutOfBoundsException(start + ":" + length);
1042
1043			return contentMgr.getText(start,length);
1044		}
1045		finally
1046		{
1047			readUnlock();
1048		}
1049	} //}}}
1050
1051	//{{{ getText() method
1052	/**
1053	 * Returns the specified text range.
1054	 * @param start The start offset
1055	 * @param length The number of characters to get
1056	 * @param seg The segment to copy the text to
1057	 */
1058	public void getText(int start, int length, Segment seg)
1059	{
1060		try
1061		{
1062			readLock();
1063
1064			if(start < 0 || length < 0
1065				|| start + length > contentMgr.getLength())
1066				throw new ArrayIndexOutOfBoundsException(start + ":" + length);
1067
1068			contentMgr.getText(start,length,seg);
1069		}
1070		finally
1071		{
1072			readUnlock();
1073		}
1074	} //}}}
1075
1076	//{{{ insert() method
1077	/**
1078	 * Inserts a string into the buffer.
1079	 * @param offset The offset
1080	 * @param str The string
1081	 * @since jEdit 4.0pre1
1082	 */
1083	public void insert(int offset, String str)
1084	{
1085		if(str == null || str.length() == 0)
1086			return;
1087
1088		if(isReadOnly())
1089			throw new RuntimeException("buffer read-only");
1090
1091		try
1092		{
1093			writeLock();
1094
1095			if(offset < 0 || offset > contentMgr.getLength())
1096				throw new ArrayIndexOutOfBoundsException(offset);
1097
1098			contentMgr.insert(offset,str);
1099
1100			integerArray.clear();
1101
1102			for(int i = 0; i < str.length(); i++)
1103			{
1104				if(str.charAt(i) == '\n')
1105					integerArray.add(i);
1106			}
1107
1108			if(!getFlag(UNDO_IN_PROGRESS))
1109			{
1110				undoMgr.contentInserted(offset,str.length(),str,
1111					!getFlag(DIRTY));
1112			}
1113
1114			contentInserted(offset,str.length(),integerArray);
1115		}
1116		finally
1117		{
1118			writeUnlock();
1119		}
1120	} //}}}
1121
1122	//{{{ insert() method
1123	/**
1124	 * Inserts a string into the buffer.
1125	 * @param offset The offset
1126	 * @param seg The segment
1127	 * @since jEdit 4.0pre1
1128	 */
1129	public void insert(int offset, Segment seg)
1130	{
1131		if(seg.count == 0)
1132			return;
1133
1134		if(isReadOnly())
1135			throw new RuntimeException("buffer read-only");
1136
1137		try
1138		{
1139			writeLock();
1140
1141			if(offset < 0 || offset > contentMgr.getLength())
1142				throw new ArrayIndexOutOfBoundsException(offset);
1143
1144			contentMgr.insert(offset,seg);
1145
1146			integerArray.clear();
1147
1148			for(int i = 0; i < seg.count; i++)
1149			{
1150				if(seg.array[seg.offset + i] == '\n')
1151					integerArray.add(i);
1152			}
1153
1154			if(!getFlag(UNDO_IN_PROGRESS))
1155			{
1156				undoMgr.contentInserted(offset,seg.count,
1157					seg.toString(),!getFlag(DIRTY));
1158			}
1159
1160			contentInserted(offset,seg.count,integerArray);
1161		}
1162		finally
1163		{
1164			writeUnlock();
1165		}
1166	} //}}}
1167
1168	//{{{ remove() method
1169	/**
1170	 * Removes the specified rang efrom the buffer.
1171	 * @param offset The start offset
1172	 * @param length The number of characters to remove
1173	 */
1174	public void remove(int offset, int length)
1175	{
1176		if(length == 0)
1177			return;
1178
1179		if(isReadOnly())
1180			throw new RuntimeException("buffer read-only");
1181
1182		try
1183		{
1184			writeLock();
1185
1186			if(offset < 0 || length < 0
1187				|| offset + length > contentMgr.getLength())
1188				throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
1189
1190			int startLine = offsetMgr.getLineOfOffset(offset);
1191
1192			contentMgr.getText(offset,length,seg);
1193			int numLines = 0;
1194			for(int i = 0; i < seg.count; i++)
1195			{
1196				if(seg.array[seg.offset + i] == '\n')
1197					numLines++;
1198			}
1199
1200			if(!getFlag(UNDO_IN_PROGRESS))
1201			{
1202				undoMgr.contentRemoved(offset,length,
1203					seg.toString(),!getFlag(DIRTY));
1204			}
1205
1206			contentMgr.remove(offset,length);
1207
1208			offsetMgr.contentRemoved(startLine,offset,numLines,length);
1209
1210			if(numLines > 0)
1211			{
1212				for(int i = 0; i < inUseFVMs.length; i++)
1213				{
1214					if(inUseFVMs[i] != null)
1215						inUseFVMs[i]._invalidate(startLine);
1216				}
1217			}
1218
1219			fireContentRemoved(startLine,offset,numLines,length);
1220
1221			setDirty(true);
1222		}
1223		finally
1224		{
1225			writeUnlock();
1226		}
1227	} //}}}
1228
1229	//}}}
1230
1231	//{{{ Undo
1232
1233	//{{{ undo() method
1234	/**
1235	 * Undoes the most recent edit.
1236	 *
1237	 * @since jEdit 4.0pre1
1238	 */
1239	public void undo(JEditTextArea textArea)
1240	{
1241		if(undoMgr == null)
1242			return;
1243
1244		if(!isEditable())
1245		{
1246			textArea.getToolkit().beep();
1247			return;
1248		}
1249
1250		try
1251		{
1252			writeLock();
1253
1254			setFlag(UNDO_IN_PROGRESS,true);
1255			if(!undoMgr.undo(textArea))
1256				textArea.getToolkit().beep();
1257
1258			fireTransactionComplete();
1259		}
1260		finally
1261		{
1262			setFlag(UNDO_IN_PROGRESS,false);
1263
1264			writeUnlock();
1265		}
1266	} //}}}
1267
1268	//{{{ redo() method
1269	/**
1270	 * Redoes the most recently undone edit. Returns true if the redo was
1271	 * successful.
1272	 *
1273	 * @since jEdit 2.7pre2
1274	 */
1275	public void redo(JEditTextArea textArea)
1276	{
1277		if(undoMgr == null)
1278			return;
1279
1280		if(!isEditable())
1281		{
1282			Toolkit.getDefaultToolkit().beep();
1283			return;
1284		}
1285
1286		try
1287		{
1288			writeLock();
1289
1290			setFlag(UNDO_IN_PROGRESS,true);
1291			if(!undoMgr.redo(textArea))
1292				textArea.getToolkit().beep();
1293
1294			fireTransactionComplete();
1295		}
1296		finally
1297		{
1298			setFlag(UNDO_IN_PROGRESS,false);
1299
1300			writeUnlock();
1301		}
1302	} //}}}
1303
1304	//{{{ isTransactionInProgress() method
1305	/**
1306	 * Returns if an undo or compound edit is currently in progress. If this
1307	 * method returns true, then eventually a
1308	 * <code>transactionComplete()</code> buffer event will get fired.
1309	 * @since jEdit 4.0pre6
1310	 */
1311	public boolean isTransactionInProgress()
1312	{
1313		return getFlag(UNDO_IN_PROGRESS) || insideCompoundEdit();
1314	} //}}}
1315
1316	//{{{ beginCompoundEdit() method
1317	/**
1318	 * Starts a compound edit. All edits from now on until
1319	 * <code>endCompoundEdit()</code> are called will be merged
1320	 * into one. This can be used to make a complex operation
1321	 * undoable in one step. Nested calls to
1322	 * <code>beginCompoundEdit()</code> behave as expected,
1323	 * requiring the same number of <code>endCompoundEdit()</code>
1324	 * calls to end the edit.
1325	 * @see #endCompoundEdit()
1326	 */
1327	public void beginCompoundEdit()
1328	{
1329		if(getFlag(TEMPORARY))
1330			return;
1331
1332		try
1333		{
1334			writeLock();
1335
1336			undoMgr.beginCompoundEdit();
1337		}
1338		finally
1339		{
1340			writeUnlock();
1341		}
1342	} //}}}
1343
1344	//{{{ endCompoundEdit() method
1345	/**
1346	 * Ends a compound edit. All edits performed since
1347	 * <code>beginCompoundEdit()</code> was called can now
1348	 * be undone in one step by calling <code>undo()</code>.
1349	 * @see #beginCompoundEdit()
1350	 */
1351	public void endCompoundEdit()
1352	{
1353		if(getFlag(TEMPORARY))
1354			return;
1355
1356		try
1357		{
1358			writeLock();
1359
1360			undoMgr.endCompoundEdit();
1361
1362			fireTransactionComplete();
1363		}
1364		finally
1365		{
1366			writeUnlock();
1367		}
1368	}//}}}
1369
1370	//{{{ insideCompoundEdit() method
1371	/**
1372	 * Returns if a compound edit is currently active.
1373	 * @since jEdit 3.1pre1
1374	 */
1375	public boolean insideCompoundEdit()
1376	{
1377		return undoMgr.insideCompoundEdit();
1378	} //}}}
1379
1380	//}}}
1381
1382	//{{{ Buffer events
1383
1384	//{{{ addBufferChangeListener() method
1385	/**
1386	 * Adds a buffer change listener.
1387	 * @param listener The listener
1388	 * @since jEdit 4.0pre1
1389	 */
1390	public void addBufferChangeListener(BufferChangeListener listener)
1391	{
1392		bufferListeners.addElement(listener);
1393	} //}}}
1394
1395	//{{{ removeBufferChangeListener() method
1396	/**
1397	 * Removes a buffer change listener.
1398	 * @param listener The listener
1399	 * @since jEdit 4.0pre1
1400	 */
1401	public void removeBufferChangeListener(BufferChangeListener listener)
1402	{
1403		bufferListeners.removeElement(listener);
1404	} //}}}
1405
1406	//{{{ getBufferChangeListeners() method
1407	/**
1408	 * Returns an array of registered buffer change listeners.
1409	 * @param listener The listener
1410	 * @since jEdit 4.1pre3
1411	 */
1412	public BufferChangeListener[] getBufferChangeListeners()
1413	{
1414		return (BufferChangeListener[])bufferListeners
1415			.toArray(new BufferChangeListener[
1416			bufferListeners.size()]);
1417	} //}}}
1418
1419	//}}}
1420
1421	//{{{ Property methods
1422
1423	//{{{ propertiesChanged() method
1424	/**
1425	 * Reloads settings from the properties. This should be called
1426	 * after the <code>syntax</code> or <code>folding</code>
1427	 * buffer-local properties are changed.
1428	 */
1429	public void propertiesChanged()
1430	{
1431		// Need to reset properties that were cached defaults,
1432		// since the defaults might have changed.
1433		Iterator iter = properties.values().iterator();
1434		while(iter.hasNext())
1435		{
1436			PropValue value = (PropValue)iter.next();
1437			if(value.defaultValue)
1438				iter.remove();
1439		}
1440
1441		textMode = "text".equals(mode.getName());
1442
1443		setTokenMarker(mode.getTokenMarker());
1444
1445		String folding = getStringProperty("folding");
1446		FoldHandler handler = FoldHandler.getFoldHandler(folding);
1447
1448		if(handler != null)
1449		{
1450			setFoldHandler(handler);
1451		}
1452		else
1453		{
1454			if (folding != null)
1455				Log.log(Log.WARNING, this, path + ": invalid 'folding' property: " + folding); 
1456			setFoldHandler(new DummyFoldHandler());
1457		}
1458
1459		if(!isTemporary() && firstTimeDone)
1460			EditBus.send(new BufferUpdate(this,null,BufferUpdate.PROPERTIES_CHANGED));
1461
1462		firstTimeDone = true;
1463	} //}}}
1464
1465	//{{{ getTabSize() method
1466	/**
1467	 * Returns the tab size used in this buffer. This is equivalent
1468	 * to calling getProperty("tabSize").
1469	 */
1470	public int getTabSize()
1471	{
1472		return getIntegerProperty("tabSize",8);
1473	} //}}}
1474
1475	//{{{ getIndentSize() method
1476	/**
1477	 * Returns the indent size used in this buffer. This is equivalent
1478	 * to calling getProperty("indentSize").
1479	 * @since jEdit 2.7pre1
1480	 */
1481	public final int getIndentSize()
1482	{
1483		return getIntegerProperty("indentSize",8);
1484	} //}}}
1485
1486	//{{{ getProperty() method
1487	/**
1488	 * Returns the value of a buffer-local property.
1489	 * @param name The property name. For backwards compatibility, this
1490	 * is an <code>Object</code>, not a <code>String</code>.
1491	 */
1492	public Object getProperty(Object name)
1493	{
1494		// First try the buffer-local properties
1495		PropValue o = (PropValue)properties.get(name);
1496		if(o != null)
1497			return o.value;
1498
1499		// For backwards compatibility
1500		if(!(name instanceof String))
1501			return null;
1502
1503		// Now try mode.<mode>.<property>
1504		if(mode != null)
1505		{
1506			Object retVal = mode.getProperty((String)name);
1507			if(retVal == null)
1508				return null;
1509
1510			properties.put(name,new PropValue(retVal,true));
1511			return retVal;
1512		}
1513		else
1514		{
1515			// Now try buffer.<property>
1516			String value = jEdit.getProperty("buffer." + name);
1517			if(value == null)
1518				return null;
1519
1520			// Try returning it as an integer first
1521			Object retVal;
1522			try
1523			{
1524				retVal = new Integer(value);
1525			}
1526			catch(NumberFormatException nf)
1527			{
1528				retVal = value;
1529			}
1530			properties.put(name,new PropValue(retVal,true));
1531			return retVal;
1532		}
1533	} //}}}
1534
1535	//{{{ setProperty() method
1536	/**
1537	 * Sets the value of a buffer-local property.
1538	 * @param name The property name
1539	 * @param value The property value
1540	 * @since jEdit 4.0pre1
1541	 */
1542	public void setProperty(String name, Object value)
1543	{
1544		if(value == null)
1545			properties.remove(name);
1546		else
1547		{
1548			PropValue test = (PropValue)properties.get(name);
1549			if(test == null)
1550				properties.put(name,new PropValue(value,false));
1551			else if(test.value.equals(value))
1552			{
1553				// do nothing
1554			}
1555			else
1556			{
1557				test.value = value;
1558				test.defaultValue = false;
1559			}
1560		}
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	//{{{ getStringProperty() method
1575	/**
1576	 * Returns the value of a string property.
1577	 * @param name The property name
1578	 * @since jEdit 4.0pre1
1579	 */
1580	public String getStringProperty(String name)
1581	{
1582		Object obj = getProperty(name);
1583		if(obj != null)
1584			return obj.toString();
1585		else
1586			return null;
1587	} //}}}
1588
1589	//{{{ setStringProperty() method
1590	/**
1591	 * Sets a string property.
1592	 * @param name The property name
1593	 * @param value The value
1594	 * @since jEdit 4.0pre1
1595	 */
1596	public void setStringProperty(String name, String value)
1597	{
1598		setProperty(name,value);
1599	} //}}}
1600
1601	//{{{ getBooleanProperty() method
1602	/**
1603	 * Returns the value of a boolean property.
1604	 * @param name The property name
1605	 * @since jEdit 4.0pre1
1606	 */
1607	public boolean getBooleanProperty(String name)
1608	{
1609		Object obj = getProperty(name);
1610		if(obj instanceof Boolean)
1611			return ((Boolean)obj).booleanValue();
1612		else if("true".equals(obj) || "on".equals(obj) || "yes".equals(obj))
1613			return true;
1614		else
1615			return false;
1616	} //}}}
1617
1618	//{{{ setBooleanProperty() method
1619	/**
1620	 * Sets a boolean property.
1621	 * @param name The property name
1622	 * @param value The value
1623	 * @since jEdit 4.0pre1
1624	 */
1625	public void setBooleanProperty(String name, boolean value)
1626	{
1627		setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
1628	} //}}}
1629
1630	//{{{ getIntegerProperty() method
1631	/**
1632	 * Returns the value of an integer property.
1633	 * @param name The property name
1634	 * @since jEdit 4.0pre1
1635	 */
1636	public int getIntegerProperty(String name, int defaultValue)
1637	{
1638		boolean defaultValueFlag;
1639		Object obj;
1640		PropValue value = (PropValue)properties.get(name);
1641		if(value != null)
1642		{
1643			obj = value.value;
1644			defaultValueFlag = value.defaultValue;
1645		}
1646		else
1647		{
1648			obj = getProperty(name);
1649			// will be cached from now on...
1650			defaultValueFlag = true;
1651		}
1652
1653		if(obj == null)
1654			return defaultValue;
1655		else if(obj instanceof Number)
1656			return ((Number)obj).intValue();
1657		else
1658		{
1659			try
1660			{
1661				int returnValue = Integer.parseInt(
1662					obj.toString().trim());
1663				properties.put(name,new PropValue(
1664					new Integer(returnValue),
1665					defaultValueFlag));
1666				return returnValue;
1667			}
1668			catch(Exception e)
1669			{
1670				return defaultValue;
1671			}
1672		}
1673	} //}}}
1674
1675	//{{{ setIntegerProperty() method
1676	/**
1677	 * Sets an integer property.
1678	 * @param name The property name
1679	 * @param value The value
1680	 * @since jEdit 4.0pre1
1681	 */
1682	public void setIntegerProperty(String name, int value)
1683	{
1684		setProperty(name,new Integer(value));
1685	} //}}}
1686
1687	//{{{ getRuleSetAtOffset() method
1688	/**
1689	 * Returns the syntax highlighting ruleset at the specified offset.
1690	 * @since jEdit 4.1pre1
1691	 */
1692	public ParserRuleSet getRuleSetAtOffset(int offset)
1693	{
1694		int line = getLineOfOffset(offset);
1695		offset -= getLineStartOffset(line);
1696		if(offset != 0)
1697			offset--;
1698
1699		DefaultTokenHandler tokens = new DefaultTokenHandler();
1700		markTokens(line,tokens);
1701		Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
1702		return token.rules;
1703	} //}}}
1704
1705	//{{{ getKeywordMapAtOffset() method
1706	/**
1707	 * Returns the syntax highlighting keyword map in effect at the
1708	 * specified offset. Used by the <b>Complete Word</b> command to
1709	 * complete keywords.
1710	 * @param offset The offset
1711	 * @since jEdit 4.0pre3
1712	 */
1713	public KeywordMap getKeywordMapAtOffset(int offset)
1714	{
1715		return getRuleSetAtOffset(offset).getKeywords();
1716	} //}}}
1717
1718	//{{{ getContextSensitiveProperty() method
1719	/**
1720	 * Some settings, like comment start and end strings, can
1721	 * vary between different parts of a buffer (HTML text and inline
1722	 * JavaScript, for example).
1723	 * @param offset The offset
1724	 * @param name The property name
1725	 * @since jEdit 4.0pre3
1726	 */
1727	public String getContextSensitiveProperty(int offset, String name)
1728	{
1729		ParserRuleSet rules = getRuleSetAtOffset(offset);
1730
1731		Object value = null;
1732
1733		Hashtable rulesetProps = rules.getProperties();
1734		if(rulesetProps != null)
1735			value = rulesetProps.get(name);
1736
1737		if(value == null)
1738		{
1739			value = rules.getMode().getProperty(name);
1740
1741			if(value == null)
1742				value = mode.getProperty(name);
1743		}
1744
1745		if(value == null)
1746			return null;
1747		else
1748			return String.valueOf(value);
1749	} //}}}
1750
1751	//{{{ Used to store property values
1752	static class PropValue
1753	{
1754		PropValue(Object value, boolean defaultValue)
1755		{
1756			if(value == null)
1757				throw new NullPointerException();
1758			this.value = value;
1759			this.defaultValue = defaultValue;
1760		}
1761
1762		Object value;
1763
1764		/**
1765		 * If this is true, then this value is cached from the mode
1766		 * or global defaults, so when the defaults change this property
1767		 * value must be reset.
1768		 */
1769		boolean defaultValue;
1770
1771		/**
1772		 * For debugging purposes.
1773		 */
1774		public String toString()
1775		{
1776			return value.toString();
1777		}
1778	} //}}}
1779
1780	//{{{ toggleWordWrap() method
1781	/**
1782	 * Toggles word wrap between the three available modes. This is used
1783	 * by the status bar.
1784	 * @param view We show a message in the view's status bar
1785	 * @since jEdit 4.1pre3
1786	 */
1787	public void toggleWordWrap(View view)
1788	{
1789		String wrap = getStringProperty("wrap");
1790		if(wrap.equals("none"))
1791			wrap = "soft";
1792		else if(wrap.equals("soft"))
1793			wrap = "hard";
1794		else if(wrap.equals("hard"))
1795			wrap = "none";
1796		view.getStatus().setMessageAndClear(jEdit.getProperty(
1797			"view.status.wrap-changed",new String[] {
1798			wrap }));
1799		setProperty("wrap",wrap);
1800		propertiesChanged();
1801	} //}}}
1802
1803	//{{{ toggleLineSeparator() method
1804	/**
1805	 * Toggles the line separator between the three available settings.
1806	 * This is used by the status bar.
1807	 * @param view We show a message in the view's status bar
1808	 * @since jEdit 4.1pre3
1809	 */
1810	public void toggleLineSeparator(View view)
1811	{
1812		String status = null;
1813		String lineSep = getStringProperty("lineSeparator");
1814		if("\n".equals(lineSep))
1815		{
1816			status = "windows";
1817			lineSep = "\r\n";
1818		}
1819		else if("\r\n".equals(lineSep))
1820		{
1821			status = "mac";
1822			lineSep = "\r";
1823		}
1824		else if("\r".equals(lineSep))
1825		{
1826			status = "unix";
1827			lineSep = "\n";
1828		}
1829		view.getStatus().setMessageAndClear(jEdit.getProperty(
1830			"view.status.linesep-changed",new String[] {
1831			jEdit.getProperty("lineSep." + status) }));
1832		setProperty("lineSeparator",lineSep);
1833		propertiesChanged();
1834	} //}}}
1835
1836	//}}}
1837
1838	//{{{ Edit modes, syntax highlighting
1839
1840	//{{{ getMode() method
1841	/**
1842	 * Returns this buffer's edit mode.
1843	 */
1844	public final Mode getMode()
1845	{
1846		return mode;
1847	} //}}}
1848
1849	//{{{ setMode() method
1850	/**
1851	 * Sets this buffer's edit mode. Note that calling this before a buffer
1852	 * is loaded will have no effect; in that case, set the "mode" property
1853	 * to the name of the mode. A bit inelegant, I know...
1854	 * @param mode The mode
1855	 */
1856	public void setMode(Mode mode)
1857	{
1858		/* This protects against stupid people (like me)
1859		 * doing stuff like buffer.setMode(jEdit.getMode(...)); */
1860		if(mode == null)
1861			throw new NullPointerException("Mode must be non-null");
1862
1863		this.mode = mode;
1864
1865		propertiesChanged(); // sets up token marker
1866	} //}}}
1867
1868	//{{{ setMode() method
1869	/**
1870	 * Sets this buffer's edit mode by calling the accept() method
1871	 * of each registered edit mode.
1872	 */
1873	public void setMode()
1874	{
1875		String userMode = getStringProperty("mode");
1876		if(userMode != null)
1877		{
1878			Mode m = jEdit.getMode(userMode);
1879			if(m != null)
1880			{
1881				setMode(m);
1882				return;
1883			}
1884		}
1885
1886		String nogzName = name.substring(0,name.length() -
1887			(name.endsWith(".gz") ? 3 : 0));
1888		Mode[] modes = jEdit.getModes();
1889
1890		String firstLine = getLineText(0);
1891
1892		for(int i = 0; i < modes.length; i++)
1893		{
1894			if(modes[i].accept(nogzName,firstLine))
1895			{
1896				setMode(modes[i]);
1897				return;
1898			}
1899		}
1900
1901		Mode defaultMode = jEdit.getMode(jEdit.getProperty("buffer.defaultMode"));
1902		if(defaultMode == null)
1903			defaultMode = jEdit.getMode("text");
1904		setMode(defaultMode);
1905	} //}}}
1906
1907	//{{{ markTokens() method
1908	/**
1909	 * Returns the syntax tokens for the specified line.
1910	 * @param lineIndex The line number
1911	 * @param tokenHandler The token handler that will receive the syntax
1912	 * tokens
1913	 * @since jEdit 4.1pre1
1914	 */
1915	public void markTokens(int lineIndex, TokenHandler tokenHandler)
1916	{
1917		Segment seg;
1918		if(SwingUtilities.isEventDispatchThread())
1919			seg = this.seg;
1920		else
1921			seg = new Segment();
1922
1923		try
1924		{
1925			writeLock();
1926
1927			if(lineIndex < 0 || lineIndex >= offsetMgr.getLineCount())
1928				throw new ArrayIndexOutOfBoundsException(lineIndex);
1929
1930			/*
1931			 * Scan backwards, looking for a line with
1932			 * a valid line context.
1933			 */
1934			int start, end;
1935			if(textMode)
1936			{
1937				start = lineIndex;
1938			}
1939			else
1940			{
1941				start = 0;
1942
1943				for(int i = lineIndex - 1; i >= 0; i--)
1944				{
1945					if(offsetMgr.isLineContextValid(i))
1946					{
1947						start = i;
1948						break;
1949					}
1950				}
1951			}
1952
1953			for(int i = start; i <= lineIndex; i++)
1954			{
1955				getLineText(i,seg);
1956
1957				TokenMarker.LineContext prevContext = (i == 0 ? null
1958					: offsetMgr.getLineContext(i - 1));
1959
1960				TokenMarker.LineContext context = offsetMgr.getLineContext(i);
1961				ParserRule oldRule;
1962				ParserRuleSet oldRules;
1963				if(context == null)
1964				{
1965					oldRule = null;
1966					oldRules = null;
1967				}
1968				else
1969				{
1970					oldRule = context.inRule;
1971					oldRules = context.rules;
1972				}
1973
1974				context = tokenMarker.markTokens(prevContext,
1975					(i == lineIndex ? tokenHandler
1976					: DummyTokenHandler.INSTANCE),seg);
1977				offsetMgr.setLineContext(i,context);
1978
1979				// Could incorrectly be set to 'false' with
1980				// recursive delegates, where the chaining might
1981				// have changed but not the rule set in question (?)
1982				if(oldRule != context.inRule)
1983					nextLineRequested = true;
1984				else if(oldRules != context.rules)
1985					nextLineRequested = true;
1986				//else if(i != lastTokenizedLine)
1987				//	nextLineRequested = false;
1988			}
1989
1990			int lineCount = offsetMgr.getLineCount();
1991			if(nextLineRequested && lineCount - lineIndex > 1)
1992			{
1993				offsetMgr.lineInfoChangedFrom(lineIndex + 1);
1994			}
1995		}
1996		finally
1997		{
1998			writeUnlock();
1999		}
2000	} //}}}
2001
2002	//{{{ isNextLineRequested() method
2003	/**
2004	 * Returns true if the next line should be repainted. This
2005	 * will return true after a line has been tokenized that starts
2006	 * a multiline token that continues onto the next line.
2007	 */
2008	public boolean isNextLineRequested()
2009	{
2010		boolean retVal = nextLineRequested;
2011		nextLineRequested = false;
2012		return retVal;
2013	} //}}}
2014
2015	//{{{ getTokenMarker() method
2016	/**
2017	 * This method is only public so that the <code>OffsetManager</code>
2018	 * class can use it.
2019	 * @since jEdit 4.0pre1
2020	 */
2021	public TokenMarker getTokenMarker()
2022	{
2023		return tokenMarker;
2024	} //}}}
2025
2026	//}}}
2027
2028	//{{{ Indentation
2029
2030	//{{{ removeTrailingWhiteSpace() method
2031	/**
2032	 * Removes trailing whitespace from all lines in the specified list.
2033	 * @param lines The line numbers
2034	 * @since jEdit 3.2pre1
2035	 */
2036	public void removeTrailingWhiteSpace(int[] lines)
2037	{
2038		try
2039		{
2040			beginCompoundEdit();
2041
2042			for(int i = 0; i < lines.length; i++)
2043			{
2044				int pos, lineStart, lineEnd, tail;
2045
2046				getLineText(lines[i],seg);
2047
2048				// blank line
2049				if (seg.count == 0) continue;
2050
2051				lineStart = seg.offset;
2052				lineEnd = seg.offset + seg.count - 1;
2053
2054				for (pos = lineEnd; pos >= lineStart; pos--)
2055				{
2056					if (!Character.isWhitespace(seg.array[pos]))
2057						break;
2058				}
2059
2060				tail = lineEnd - pos;
2061
2062				// no whitespace
2063				if (tail == 0) continue;
2064
2065				remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
2066			}
2067		}
2068		finally
2069		{
2070			endCompoundEdit();
2071		}
2072	} //}}}
2073
2074	//{{{ shiftIndentLeft() method
2075	/**
2076	 * Shifts the indent of each line in the specified list to the left.
2077	 * @param lines The line numbers
2078	 * @since jEdit 3.2pre1
2079	 */
2080	public void shiftIndentLeft(int[] lines)
2081	{
2082		int tabSize = getTabSize();
2083		int indentSize = getIndentSize();
2084		boolean noTabs = getBooleanProperty("noTabs");
2085
2086		try
2087		{
2088			beginCompoundEdit();
2089
2090			for(int i = 0; i < lines.length; i++)
2091			{
2092				int lineStart = getLineStartOffset(lines[i]);
2093				String line = getLineText(lines[i]);
2094				int whiteSpace = MiscUtilities
2095					.getLeadingWhiteSpace(line);
2096				if(whiteSpace == 0)
2097					continue;
2098				int whiteSpaceWidth = Math.max(0,MiscUtilities
2099					.getLeadingWhiteSpaceWidth(line,tabSize)
2100					- indentSize);
2101	
2102				insert(lineStart + whiteSpace,MiscUtilities
2103					.createWhiteSpace(whiteSpaceWidth,
2104					(noTabs ? 0 : tabSize)));
2105				remove(lineStart,whiteSpace);
2106			}
2107
2108		}
2109		finally
2110		{
2111			endCompoundEdit();
2112		}
2113	} //}}}
2114
2115	//{{{ shiftIndentRight() method
2116	/**
2117	 * Shifts the indent of each line in the specified list to the right.
2118	 * @param lines The line numbers
2119	 * @since jEdit 3.2pre1
2120	 */
2121	public void shiftIndentRight(int[] lines)
2122	{
2123		try
2124		{
2125			beginCompoundEdit();
2126
2127			int tabSize = getTabSize();
2128			int indentSize = getIndentSize();
2129			boolean noTabs = getBooleanProperty("noTabs");
2130			for(int i = 0; i < lines.length; i++)
2131			{
2132				int lineStart = getLineStartOffset(lines[i]);
2133				String line = getLineText(lines[i]);
2134				int whiteSpace = MiscUtilities
2135					.getLeadingWhiteSpace(line);
2136
2137				// silly usability hack
2138				//if(lines.length != 1 && whiteSpace == 0)
2139				//	continue;
2140
2141				int whiteSpaceWidth = MiscUtilities
2142					.getLeadingWhiteSpaceWidth(
2143					line,tabSize) + indentSize;
2144				insert(lineStart + whiteSpace,MiscUtilities
2145					.createWhiteSpace(whiteSpaceWidth,
2146					(noTabs ? 0 : tabSize)));
2147				remove(lineStart,whiteSpace);
2148			}
2149		}
2150		finally
2151		{
2152			endCompoundEdit();
2153		}
2154	} //}}}
2155
2156	//{{{ indentLine() method
2157	/**
2158	 * If auto indent is enabled, this method is called when the `Tab'
2159	 * or `Enter' key is pressed to perform mode-specific indentation
2160	 * and return true, or return false if a normal tab is to be inserted.
2161	 * @param line The line number to indent
2162	 * @param canIncreaseIndent If false, nothing will be done if the
2163	 * calculated indent is greater than the current
2164	 * @param canDecreaseIndent If false, nothing will be done if the
2165	 * calculated indent is less than the current
2166	 * @return true if the tab key event should be swallowed (ignored)
2167	 * false if a real tab should be inserted
2168	 */
2169	public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
2170		boolean canDecreaseIndent)
2171	{
2172		getLineText(lineIndex,seg);
2173
2174		int tabSize = getTabSize();
2175
2176		int whitespaceChars = 0;
2177		int currentIndent = 0;
2178loop:		for(int i = 0; i < seg.count; i++)
2179		{
2180			char c = seg.array[seg.offset + i];
2181			switch(c)
2182			{
2183			case ' ':
2184				currentIndent++;
2185				whitespaceChars++;
2186				break;
2187			case '\t':
2188				currentIndent += (tabSize - (currentIndent
2189					% tabSize));
2190				whitespaceChars++;
2191				break;
2192			default:
2193				break loop;
2194			}
2195		}
2196
2197		int idealIndent = getIndentForLine(lineIndex);
2198		if(idealIndent == -1)
2199			return false;
2200
2201		if(!canDecreaseIndent && idealIndent <= currentIndent)
2202			return false;
2203
2204		if(!canIncreaseIndent && idealIndent >= currentIndent)
2205			return false;
2206
2207		// Do it
2208		try
2209		{
2210			beginCompoundEdit();
2211
2212			int start = getLineStartOffset(lineIndex);
2213
2214			remove(start,whitespaceChars);
2215			insert(start,MiscUtilities.createWhiteSpace(
2216				idealIndent,(getBooleanProperty("noTabs")
2217				? 0 : tabSize)));
2218		}
2219		finally
2220		{
2221			endCompoundEdit();
2222		}
2223
2224		return true;
2225	} //}}}
2226
2227	//{{{ indentLines() method
2228	/**
2229	 * Indents all specified lines.
2230	 * @param start The first line to indent
2231	 * @param end The last line to indent
2232	 * @since jEdit 3.1pre3
2233	 */
2234	public void indentLines(int start, int end)
2235	{
2236		try
2237		{
2238			beginCompoundEdit();
2239			for(int i = start; i <= end; i++)
2240				indentLine(i,true,true);
2241		}
2242		finally
2243		{
2244			endCompoundEdit();
2245		}
2246	} //}}}
2247
2248	//{{{ indentLines() method
2249	/**
2250	 * Indents all specified lines.
2251	 * @param lines The line numbers
2252	 * @since jEdit 3.2pre1
2253	 */
2254	public void indentLines(int[] lines)
2255	{
2256		try
2257		{
2258			beginCompoundEdit();
2259			for(int i = 0

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