PageRenderTime 225ms CodeModel.GetById 20ms app.highlight 182ms RepoModel.GetById 3ms app.codeStats 0ms

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

#
Java | 2963 lines | 1716 code | 394 blank | 853 comment | 336 complexity | 72b6a2e041a396c0049511142b975b71 MD5 | raw file
Possible License(s): BSD-3-Clause, AGPL-1.0, Apache-2.0, LGPL-2.0, LGPL-3.0, GPL-2.0, CC-BY-SA-3.0, LGPL-2.1, GPL-3.0, MPL-2.0-no-copyleft-exception, IPL-1.0

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

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