PageRenderTime 238ms CodeModel.GetById 107ms app.highlight 113ms RepoModel.GetById 1ms app.codeStats 2ms

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

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

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