PageRenderTime 249ms CodeModel.GetById 120ms app.highlight 111ms RepoModel.GetById 1ms app.codeStats 1ms

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

#
Java | 2985 lines | 1684 code | 391 blank | 910 comment | 359 complexity | e7e005124e51ebf6a5314a7c2f6d3ac7 MD5 | raw file

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

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

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