PageRenderTime 231ms CodeModel.GetById 88ms app.highlight 112ms RepoModel.GetById 1ms app.codeStats 1ms

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

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

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