Docjar: A Java Source and Docuemnt Enginecom.*    java.*    javax.*    org.*    all    new    plug-in

Quick Search    Search Deep

Source code: org/gjt/sp/jedit/Buffer.java


1   /*
2    * Buffer.java - jEdit buffer
3    * :tabSize=8:indentSize=8:noTabs=false:
4    * :folding=explicit:collapseFolds=1:
5    *
6    * Copyright (C) 1998, 2003 Slava Pestov
7    * Portions copyright (C) 1999, 2000 mike dillon
8    *
9    * This program is free software; you can redistribute it and/or
10   * modify it under the terms of the GNU General Public License
11   * as published by the Free Software Foundation; either version 2
12   * of the License, or any later version.
13   *
14   * This program is distributed in the hope that it will be useful,
15   * but WITHOUT ANY WARRANTY; without even the implied warranty of
16   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
17   * GNU General Public License for more details.
18   *
19   * You should have received a copy of the GNU General Public License
20   * along with this program; if not, write to the Free Software
21   * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
22   */
23  
24  package org.gjt.sp.jedit;
25  
26  //{{{ Imports
27  import gnu.regexp.*;
28  import javax.swing.*;
29  import javax.swing.text.*;
30  import java.awt.Toolkit;
31  import java.io.File;
32  import java.io.IOException;
33  import java.net.Socket;
34  import java.util.*;
35  import org.gjt.sp.jedit.browser.VFSBrowser;
36  import org.gjt.sp.jedit.buffer.*;
37  import org.gjt.sp.jedit.io.*;
38  import org.gjt.sp.jedit.msg.*;
39  import org.gjt.sp.jedit.search.RESearchMatcher;
40  import org.gjt.sp.jedit.syntax.*;
41  import org.gjt.sp.jedit.textarea.*;
42  import org.gjt.sp.util.*;
43  //}}}
44  
45  /**
46   * A <code>Buffer</code> represents the contents of an open text
47   * file as it is maintained in the computer's memory (as opposed to
48   * how it may be stored on a disk).<p>
49   *
50   * In a BeanShell script, you can obtain the current buffer instance from the
51   * <code>buffer</code> variable.<p>
52   *
53   * This class does not have a public constructor.
54   * Buffers can be opened and closed using methods in the <code>jEdit</code>
55   * class.<p>
56   *
57   * This class is partially thread-safe, however you must pay attention to two
58   * very important guidelines:
59   * <ul>
60   * <li>Changes to a buffer can only be made from the AWT thread.
61   * <li>When accessing the buffer from another thread, you must
62   * grab a read lock if you plan on performing more than one call, to ensure that
63   * the buffer contents are not changed by the AWT thread for the duration of the
64   * lock. Only methods whose descriptions specify thread safety can be invoked
65   * from other threads.
66   * </ul>
67   *
68   * @author Slava Pestov
69   * @version $Id: Buffer.java,v 1.208 2003/11/22 20:32:28 spestov Exp $
70   */
71  public class Buffer
72  {
73    //{{{ Some constants
74    /**
75     * Line separator property.
76     */
77    public static final String LINESEP = "lineSeparator";
78  
79    /**
80     * Backed up property.
81     * @since jEdit 3.2pre2
82     */
83    public static final String BACKED_UP = "Buffer__backedUp";
84  
85    /**
86     * Caret info properties.
87     * @since jEdit 3.2pre1
88     */
89    public static final String CARET = "Buffer__caret";
90    public static final String SELECTION = "Buffer__selection";
91  
92    /**
93     * This should be a physical line number, so that the scroll
94     * position is preserved correctly across reloads (which will
95     * affect virtual line numbers, due to fold being reset)
96     */
97    public static final String SCROLL_VERT = "Buffer__scrollVert";
98    public static final String SCROLL_HORIZ = "Buffer__scrollHoriz";
99  
100   /**
101    * Character encoding used when loading and saving.
102    * @since jEdit 3.2pre4
103    */
104   public static final String ENCODING = "encoding";
105 
106   /**
107    * Should jEdit try to set the encoding based on a UTF8, UTF16 or
108    * XML signature at the beginning of the file?
109    */
110   public static final String ENCODING_AUTODETECT = "encodingAutodetect";
111 
112   /**
113    * This property is set to 'true' if the file has a trailing newline.
114    * @since jEdit 4.0pre1
115    */
116   public static final String TRAILING_EOL = "trailingEOL";
117 
118   /**
119    * This property is set to 'true' if the file should be GZipped.
120    * @since jEdit 4.0pre4
121    */
122   public static final String GZIPPED = "gzipped";
123   //}}}
124 
125   //{{{ Input/output methods
126 
127   //{{{ reload() method
128   /**
129    * Reloads the buffer from disk, asking for confirmation if the buffer
130    * has unsaved changes.
131    * @param view The view
132    * @since jEdit 2.7pre2
133    */
134   public void reload(View view)
135   {
136     if(getFlag(DIRTY))
137     {
138       String[] args = { name };
139       int result = GUIUtilities.confirm(view,"changedreload",
140         args,JOptionPane.YES_NO_OPTION,
141         JOptionPane.WARNING_MESSAGE);
142       if(result != JOptionPane.YES_OPTION)
143         return;
144     }
145 
146     view.getEditPane().saveCaretInfo();
147     load(view,true);
148   } //}}}
149 
150   //{{{ load() method
151   /**
152    * Loads the buffer from disk, even if it is loaded already.
153    * @param view The view
154    * @param reload If true, user will not be asked to recover autosave
155    * file, if any
156    *
157    * @since 2.5pre1
158    */
159   public boolean load(final View view, final boolean reload)
160   {
161     if(isPerformingIO())
162     {
163       GUIUtilities.error(view,"buffer-multiple-io",null);
164       return false;
165     }
166 
167     setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
168 
169     setFlag(LOADING,true);
170 
171     // view text areas temporarily blank out while a buffer is
172     // being loaded, to indicate to the user that there is no
173     // data available yet.
174     if(!getFlag(TEMPORARY))
175       EditBus.send(new BufferUpdate(this,view,BufferUpdate.LOAD_STARTED));
176 
177     final boolean loadAutosave;
178 
179     if(reload || !getFlag(NEW_FILE))
180     {
181       if(file != null)
182         modTime = file.lastModified();
183 
184       // Only on initial load
185       if(!reload && autosaveFile != null && autosaveFile.exists())
186         loadAutosave = recoverAutosave(view);
187       else
188       {
189         if(autosaveFile != null)
190           autosaveFile.delete();
191         loadAutosave = false;
192       }
193 
194       if(!loadAutosave)
195       {
196         VFS vfs = VFSManager.getVFSForPath(path);
197 
198         if(!checkFileForLoad(view,vfs,path))
199         {
200           setFlag(LOADING,false);
201           return false;
202         }
203 
204         // have to check again since above might set
205         // NEW_FILE flag
206         if(reload || !getFlag(NEW_FILE))
207         {
208           if(!vfs.load(view,this,path))
209           {
210             setFlag(LOADING,false);
211             return false;
212           }
213         }
214       }
215     }
216     else
217       loadAutosave = false;
218 
219     //{{{ Do some stuff once loading is finished
220     Runnable runnable = new Runnable()
221     {
222       public void run()
223       {
224         String newPath = getStringProperty(
225           BufferIORequest.NEW_PATH);
226         Segment seg = (Segment)getProperty(
227           BufferIORequest.LOAD_DATA);
228         IntegerArray endOffsets = (IntegerArray)
229           getProperty(BufferIORequest.END_OFFSETS);
230 
231         if(seg == null)
232           seg = new Segment(new char[1024],0,0);
233         if(endOffsets == null)
234         {
235           endOffsets = new IntegerArray();
236           endOffsets.add(1);
237         }
238 
239         try
240         {
241           writeLock();
242 
243           // For `reload' command
244           firePreContentRemoved(0,0,getLineCount()
245             - 1,getLength());
246 
247           contentMgr.remove(0,getLength());
248           lineMgr.contentRemoved(0,0,getLineCount()
249             - 1,getLength());
250           positionMgr.contentRemoved(0,getLength());
251           fireContentRemoved(0,0,getLineCount()
252             - 1,getLength());
253 
254           // theoretically a segment could
255           // have seg.offset != 0 but
256           // SegmentBuffer never does that
257           contentMgr._setContent(seg.array,seg.count);
258 
259           lineMgr._contentInserted(endOffsets);
260           positionMgr.contentInserted(0,seg.count);
261 
262           fireContentInserted(0,0,
263             endOffsets.getSize() - 1,
264             seg.count - 1);
265         }
266         finally
267         {
268           writeUnlock();
269         }
270 
271         unsetProperty(BufferIORequest.LOAD_DATA);
272         unsetProperty(BufferIORequest.END_OFFSETS);
273         unsetProperty(BufferIORequest.NEW_PATH);
274 
275         undoMgr.clear();
276         undoMgr.setLimit(jEdit.getIntegerProperty(
277           "buffer.undoCount",100));
278 
279         if(!getFlag(TEMPORARY))
280           finishLoading();
281 
282         setFlag(LOADING,false);
283 
284         // if reloading a file, clear dirty flag
285         if(reload)
286           setDirty(false);
287 
288         if(!loadAutosave && newPath != null)
289           setPath(newPath);
290 
291         // if loadAutosave is false, we loaded an
292         // autosave file, so we set 'dirty' to true
293 
294         // note that we don't use setDirty(),
295         // because a) that would send an unnecessary
296         // message, b) it would also set the
297         // AUTOSAVE_DIRTY flag, which will make
298         // the autosave thread write out a
299         // redundant autosave file
300         if(loadAutosave)
301           setFlag(DIRTY,true);
302 
303         // send some EditBus messages
304         if(!getFlag(TEMPORARY))
305         {
306           EditBus.send(new BufferUpdate(Buffer.this,
307             view,BufferUpdate.LOADED));
308           //EditBus.send(new BufferUpdate(Buffer.this,
309           //  view,BufferUpdate.MARKERS_CHANGED));
310         }
311       }
312     }; //}}}
313 
314     if(getFlag(TEMPORARY))
315       runnable.run();
316     else
317       VFSManager.runInAWTThread(runnable);
318 
319     return true;
320   } //}}}
321 
322   //{{{ insertFile() method
323   /**
324    * Loads a file from disk, and inserts it into this buffer.
325    * @param view The view
326    *
327    * @since 4.0pre1
328    */
329   public boolean insertFile(final View view, String path)
330   {
331     if(isPerformingIO())
332     {
333       GUIUtilities.error(view,"buffer-multiple-io",null);
334       return false;
335     }
336 
337     setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
338 
339     path = MiscUtilities.constructPath(this.path,path);
340 
341     Buffer buffer = jEdit.getBuffer(path);
342     if(buffer != null)
343     {
344       view.getTextArea().setSelectedText(
345         buffer.getText(0,buffer.getLength()));
346       return true;
347     }
348 
349     VFS vfs = VFSManager.getVFSForPath(path);
350 
351     // this returns false if initial sanity
352     // checks (if the file is a directory, etc)
353     // fail
354     return vfs.insert(view,this,path);
355   } //}}}
356 
357   //{{{ autosave() method
358   /**
359    * Autosaves this buffer.
360    */
361   public void autosave()
362   {
363     if(autosaveFile == null || !getFlag(AUTOSAVE_DIRTY)
364       || !getFlag(DIRTY)
365       || getFlag(LOADING)
366       || getFlag(IO))
367       return;
368 
369     setFlag(AUTOSAVE_DIRTY,false);
370 
371     VFSManager.runInWorkThread(new BufferIORequest(
372       BufferIORequest.AUTOSAVE,null,this,null,
373       VFSManager.getFileVFS(),autosaveFile.getPath()));
374   } //}}}
375 
376   //{{{ saveAs() method
377   /**
378    * Prompts the user for a file to save this buffer to.
379    * @param view The view
380    * @param rename True if the buffer's path should be changed, false
381    * if only a copy should be saved to the specified filename
382    * @since jEdit 2.6pre5
383    */
384   public boolean saveAs(View view, boolean rename)
385   {
386     String[] files = GUIUtilities.showVFSFileDialog(view,path,
387       VFSBrowser.SAVE_DIALOG,false);
388 
389     // files[] should have length 1, since the dialog type is
390     // SAVE_DIALOG
391     if(files == null)
392       return false;
393 
394     return save(view,files[0],rename);
395   } //}}}
396 
397   //{{{ save() method
398   /**
399    * Saves this buffer to the specified path name, or the current path
400    * name if it's null.
401    * @param view The view
402    * @param path The path name to save the buffer to, or null to use
403    * the existing path
404    */
405   public boolean save(View view, String path)
406   {
407     return save(view,path,true);
408   } //}}}
409 
410   //{{{ save() method
411   /**
412    * Saves this buffer to the specified path name, or the current path
413    * name if it's null.
414    * @param view The view
415    * @param path The path name to save the buffer to, or null to use
416    * the existing path
417    * @param rename True if the buffer's path should be changed, false
418    * if only a copy should be saved to the specified filename
419    * @since jEdit 2.6pre5
420    */
421   public boolean save(final View view, String path, final boolean rename)
422   {
423     if(isPerformingIO())
424     {
425       GUIUtilities.error(view,"buffer-multiple-io",null);
426       return false;
427     }
428 
429     setBooleanProperty(BufferIORequest.ERROR_OCCURRED,false);
430 
431     if(path == null && getFlag(NEW_FILE))
432       return saveAs(view,rename);
433 
434     if(path == null && file != null)
435     {
436       long newModTime = file.lastModified();
437 
438       if(newModTime != modTime
439         && jEdit.getBooleanProperty("view.checkModStatus"))
440       {
441         Object[] args = { this.path };
442         int result = GUIUtilities.confirm(view,
443           "filechanged-save",args,
444           JOptionPane.YES_NO_OPTION,
445           JOptionPane.WARNING_MESSAGE);
446         if(result != JOptionPane.YES_OPTION)
447           return false;
448       }
449     }
450 
451     setFlag(IO,true);
452     EditBus.send(new BufferUpdate(this,view,BufferUpdate.SAVING));
453 
454     final String oldPath = this.path;
455     final String oldSymlinkPath = this.symlinkPath;
456     final String newPath = (path == null ? this.path : path);
457 
458     VFS vfs = VFSManager.getVFSForPath(newPath);
459 
460     if(!checkFileForSave(view,vfs,newPath))
461     {
462       setFlag(IO,false);
463       return false;
464     }
465 
466     if(!vfs.save(view,this,newPath))
467     {
468       setFlag(IO,false);
469       return false;
470     }
471 
472     // Once save is complete, do a few other things
473     VFSManager.runInAWTThread(new Runnable()
474     {
475       public void run()
476       {
477         setFlag(IO,false);
478         finishSaving(view,oldPath,oldSymlinkPath,
479           newPath,rename,getBooleanProperty(
480           BufferIORequest.ERROR_OCCURRED));
481       }
482     });
483 
484     return true;
485   } //}}}
486 
487   //{{{ checkFileStatus() method
488   public static final int FILE_NOT_CHANGED = 0;
489   public static final int FILE_CHANGED = 1;
490   public static final int FILE_DELETED = 2;
491   /**
492    * Check if the buffer has changed on disk.
493    * @return One of <code>NOT_CHANGED</code>, <code>CHANGED</code>, or
494    * <code>DELETED</code>.
495    *
496    * @since jEdit 4.2pre1
497    */
498   public int checkFileStatus(View view)
499   {
500     // - don't do these checks while a save is in progress,
501     // because for a moment newModTime will be greater than
502     // oldModTime, due to the multithreading
503     // - only supported on local file system
504     if(!getFlag(IO) && !getFlag(LOADING) && file != null
505       && !getFlag(NEW_FILE))
506     {
507       boolean newReadOnly = (file.exists() && !file.canWrite());
508       if(newReadOnly != getFlag(READ_ONLY))
509       {
510         setFlag(READ_ONLY,newReadOnly);
511         EditBus.send(new BufferUpdate(this,null,
512           BufferUpdate.DIRTY_CHANGED));
513       }
514 
515       long oldModTime = modTime;
516       long newModTime = file.lastModified();
517 
518       if(newModTime != oldModTime)
519       {
520         modTime = newModTime;
521 
522         if(!file.exists())
523         {
524           setFlag(NEW_FILE,true);
525           setDirty(true);
526           return FILE_DELETED;
527         }
528         else
529         {
530           return FILE_CHANGED;
531         }
532       }
533     }
534 
535     return FILE_NOT_CHANGED;
536   } //}}}
537 
538   //}}}
539 
540   //{{{ Getters/setter methods for various buffer meta-data
541 
542   //{{{ getLastModified() method
543   /**
544    * Returns the last time jEdit modified the file on disk.
545    * This method is thread-safe.
546    */
547   public long getLastModified()
548   {
549     return modTime;
550   } //}}}
551 
552   //{{{ setLastModified() method
553   /**
554    * Sets the last time jEdit modified the file on disk.
555    * @param modTime The new modification time
556    */
557   public void setLastModified(long modTime)
558   {
559     this.modTime = modTime;
560   } //}}}
561 
562   //{{{ getVFS() method
563   /**
564    * Returns the virtual filesystem responsible for loading and
565    * saving this buffer. This method is thread-safe.
566    */
567   public VFS getVFS()
568   {
569     return VFSManager.getVFSForPath(path);
570   } //}}}
571 
572   //{{{ getAutosaveFile() method
573   /**
574    * Returns the autosave file for this buffer. This may be null if
575    * the file is non-local.
576    */
577   public File getAutosaveFile()
578   {
579     return autosaveFile;
580   } //}}}
581 
582   //{{{ getName() method
583   /**
584    * Returns the name of this buffer. This method is thread-safe.
585    */
586   public String getName()
587   {
588     return name;
589   } //}}}
590 
591   //{{{ getPath() method
592   /**
593    * Returns the path name of this buffer. This method is thread-safe.
594    */
595   public String getPath()
596   {
597     return path;
598   } //}}}
599 
600   //{{{ getSymlinkPath() method
601   /**
602    * If this file is a symbolic link, returns the link destination.
603    * Otherwise returns the file's path. This method is thread-safe.
604    * @since jEdit 4.2pre1
605    */
606   public String getSymlinkPath()
607   {
608     return symlinkPath;
609   } //}}}
610 
611   //{{{ getDirectory() method
612   /**
613    * Returns the directory containing this buffer.
614    * @since jEdit 4.1pre11
615    */
616   public String getDirectory()
617   {
618     return directory;
619   } //}}}
620 
621   //{{{ isClosed() method
622   /**
623    * Returns true if this buffer has been closed with
624    * {@link org.gjt.sp.jedit.jEdit#closeBuffer(View,Buffer)}.
625    * This method is thread-safe.
626    */
627   public boolean isClosed()
628   {
629     return getFlag(CLOSED);
630   } //}}}
631 
632   //{{{ isLoaded() method
633   /**
634    * Returns true if the buffer is loaded. This method is thread-safe.
635    */
636   public boolean isLoaded()
637   {
638     return !getFlag(LOADING);
639   } //}}}
640 
641   //{{{ isPerformingIO() method
642   /**
643    * Returns true if the buffer is currently performing I/O.
644    * This method is thread-safe.
645    * @since jEdit 2.7pre1
646    */
647   public boolean isPerformingIO()
648   {
649     return getFlag(LOADING) || getFlag(IO);
650   } //}}}
651 
652   //{{{ isNewFile() method
653   /**
654    * Returns whether this buffer lacks a corresponding version on disk.
655    * This method is thread-safe.
656    */
657   public boolean isNewFile()
658   {
659     return getFlag(NEW_FILE);
660   } //}}}
661 
662   //{{{ setNewFile() method
663   /**
664    * Sets the new file flag.
665    * @param newFile The new file flag
666    */
667   public void setNewFile(boolean newFile)
668   {
669     setFlag(NEW_FILE,newFile);
670     if(!newFile)
671       setFlag(UNTITLED,false);
672   } //}}}
673 
674   //{{{ isUntitled() method
675   /**
676    * Returns true if this file is 'untitled'. This method is thread-safe.
677    */
678   public boolean isUntitled()
679   {
680     return getFlag(UNTITLED);
681   } //}}}
682 
683   //{{{ isDirty() method
684   /**
685    * Returns whether there have been unsaved changes to this buffer.
686    * This method is thread-safe.
687    */
688   public boolean isDirty()
689   {
690     return getFlag(DIRTY);
691   } //}}}
692 
693   //{{{ isReadOnly() method
694   /**
695    * Returns true if this file is read only, false otherwise.
696    * This method is thread-safe.
697    */
698   public boolean isReadOnly()
699   {
700     return getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE);
701   } //}}}
702 
703   //{{{ isEditable() method
704   /**
705    * Returns true if this file is editable, false otherwise. A file may
706    * become uneditable if it is read only, or if I/O is in progress.
707    * This method is thread-safe.
708    * @since jEdit 2.7pre1
709    */
710   public boolean isEditable()
711   {
712     return !(getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE)
713       || getFlag(IO) || getFlag(LOADING));
714   } //}}}
715 
716   //{{{ setReadOnly() method
717   /**
718    * Sets the read only flag.
719    * @param readOnly The read only flag
720    */
721   public void setReadOnly(boolean readOnly)
722   {
723     setFlag(READ_ONLY_OVERRIDE,readOnly);
724   } //}}}
725 
726   //{{{ setDirty() method
727   /**
728    * Sets the 'dirty' (changed since last save) flag of this buffer.
729    */
730   public void setDirty(boolean d)
731   {
732     boolean old_d = getFlag(DIRTY);
733     boolean editable = isEditable();
734 
735     if(d)
736     {
737       if(editable)
738       {
739         setFlag(DIRTY,true);
740         setFlag(AUTOSAVE_DIRTY,true);
741       }
742     }
743     else
744     {
745       setFlag(DIRTY,false);
746       setFlag(AUTOSAVE_DIRTY,false);
747 
748       if(autosaveFile != null)
749         autosaveFile.delete();
750 
751       // fixes dirty flag not being reset on
752       // save/insert/undo/redo/undo
753       if(!getFlag(UNDO_IN_PROGRESS))
754       {
755         // this ensures that undo can clear the dirty flag properly
756         // when all edits up to a save are undone
757         undoMgr.bufferSaved();
758       }
759     }
760 
761     if(d != old_d && editable)
762     {
763       EditBus.send(new BufferUpdate(this,null,
764         BufferUpdate.DIRTY_CHANGED));
765     }
766   } //}}}
767 
768   //{{{ isTemporary() method
769   /**
770    * Returns if this is a temporary buffer. This method is thread-safe.
771    * @see jEdit#openTemporary(View,String,String,boolean)
772    * @see jEdit#commitTemporary(Buffer)
773    * @since jEdit 2.2pre7
774    */
775   public boolean isTemporary()
776   {
777     return getFlag(TEMPORARY);
778   } //}}}
779 
780   //{{{ getIcon() method
781   /**
782    * Returns this buffer's icon.
783    * @since jEdit 2.6pre6
784    */
785   public Icon getIcon()
786   {
787     if(getFlag(DIRTY))
788       return GUIUtilities.DIRTY_BUFFER_ICON;
789     else if(getFlag(READ_ONLY) || getFlag(READ_ONLY_OVERRIDE))
790       return GUIUtilities.READ_ONLY_BUFFER_ICON;
791     else if(getFlag(NEW_FILE))
792       return GUIUtilities.NEW_BUFFER_ICON;
793     else
794       return GUIUtilities.NORMAL_BUFFER_ICON;
795   } //}}}
796 
797   //}}}
798 
799   //{{{ Thread safety
800 
801   //{{{ readLock() method
802   /**
803    * The buffer is guaranteed not to change between calls to
804    * {@link #readLock()} and {@link #readUnlock()}.
805    */
806   public void readLock()
807   {
808     lock.readLock();
809   } //}}}
810 
811   //{{{ readUnlock() method
812   /**
813    * The buffer is guaranteed not to change between calls to
814    * {@link #readLock()} and {@link #readUnlock()}.
815    */
816   public void readUnlock()
817   {
818     lock.readUnlock();
819   } //}}}
820 
821   //{{{ writeLock() method
822   /**
823    * Attempting to obtain read lock will block between calls to
824    * {@link #writeLock()} and {@link #writeUnlock()}.
825    */
826   public void writeLock()
827   {
828     lock.writeLock();
829   } //}}}
830 
831   //{{{ writeUnlock() method
832   /**
833    * Attempting to obtain read lock will block between calls to
834    * {@link #writeLock()} and {@link #writeUnlock()}.
835    */
836   public void writeUnlock()
837   {
838     lock.writeUnlock();
839   } //}}}
840 
841   //}}}
842 
843   //{{{ Line offset methods
844 
845   //{{{ getLength() method
846   /**
847    * Returns the number of characters in the buffer. This method is thread-safe.
848    */
849   public int getLength()
850   {
851     // no need to lock since this just returns a value and that's it
852     return contentMgr.getLength();
853   } //}}}
854 
855   //{{{ getLineCount() method
856   /**
857    * Returns the number of physical lines in the buffer.
858    * This method is thread-safe.
859    * @since jEdit 3.1pre1
860    */
861   public int getLineCount()
862   {
863     // no need to lock since this just returns a value and that's it
864     return lineMgr.getLineCount();
865   } //}}}
866 
867   //{{{ getLineOfOffset() method
868   /**
869    * Returns the line containing the specified offset.
870    * This method is thread-safe.
871    * @param offset The offset
872    * @since jEdit 4.0pre1
873    */
874   public int getLineOfOffset(int offset)
875   {
876     try
877     {
878       readLock();
879 
880       if(offset < 0 || offset > getLength())
881         throw new ArrayIndexOutOfBoundsException(offset);
882 
883       return lineMgr.getLineOfOffset(offset);
884     }
885     finally
886     {
887       readUnlock();
888     }
889   } //}}}
890 
891   //{{{ getLineStartOffset() method
892   /**
893    * Returns the start offset of the specified line.
894    * This method is thread-safe.
895    * @param line The line
896    * @return The start offset of the specified line
897    * @since jEdit 4.0pre1
898    */
899   public int getLineStartOffset(int line)
900   {
901     try
902     {
903       readLock();
904 
905       if(line < 0 || line >= lineMgr.getLineCount())
906         throw new ArrayIndexOutOfBoundsException(line);
907       else if(line == 0)
908         return 0;
909 
910       return lineMgr.getLineEndOffset(line - 1);
911     }
912     finally
913     {
914       readUnlock();
915     }
916   } //}}}
917 
918   //{{{ getLineEndOffset() method
919   /**
920    * Returns the end offset of the specified line.
921    * This method is thread-safe.
922    * @param line The line
923    * @return The end offset of the specified line
924    * invalid.
925    * @since jEdit 4.0pre1
926    */
927   public int getLineEndOffset(int line)
928   {
929     try
930     {
931       readLock();
932 
933       if(line < 0 || line >= lineMgr.getLineCount())
934         throw new ArrayIndexOutOfBoundsException(line);
935 
936       return lineMgr.getLineEndOffset(line);
937     }
938     finally
939     {
940       readUnlock();
941     }
942   } //}}}
943 
944   //{{{ getLineLength() method
945   /**
946    * Returns the length of the specified line.
947    * This method is thread-safe.
948    * @param line The line
949    * @since jEdit 4.0pre1
950    */
951   public int getLineLength(int line)
952   {
953     try
954     {
955       readLock();
956 
957       return getLineEndOffset(line)
958         - getLineStartOffset(line) - 1;
959     }
960     finally
961     {
962       readUnlock();
963     }
964   } //}}}
965 
966   //{{{ invalidateCachedScreenLineCounts() method
967   /**
968    * Invalidates all cached screen line count information.
969    * @since jEdit 4.2pre7.
970    */
971   public void invalidateCachedScreenLineCounts()
972   {
973     lineMgr.invalidateScreenLineCounts();
974   } //}}}
975 
976   //}}}
977 
978   //{{{ Text getters and setters
979 
980   //{{{ getLineText() method
981   /**
982    * Returns the text on the specified line.
983    * This method is thread-safe.
984    * @param line The line
985    * @return The text, or null if the line is invalid
986    * @since jEdit 4.0pre1
987    */
988   public String getLineText(int line)
989   {
990     if(line < 0 || line >= lineMgr.getLineCount())
991       throw new ArrayIndexOutOfBoundsException(line);
992 
993     try
994     {
995       readLock();
996 
997       int start = (line == 0 ? 0
998         : lineMgr.getLineEndOffset(line - 1));
999       int end = lineMgr.getLineEndOffset(line);
1000
1001      return getText(start,end - start - 1);
1002    }
1003    finally
1004    {
1005      readUnlock();
1006    }
1007  } //}}}
1008
1009  //{{{ getLineText() method
1010  /**
1011   * Returns the specified line in a <code>Segment</code>.<p>
1012   *
1013   * Using a <classname>Segment</classname> is generally more
1014   * efficient than using a <classname>String</classname> because it
1015   * results in less memory allocation and array copying.<p>
1016   *
1017   * This method is thread-safe.
1018   *
1019   * @param line The line
1020   * @since jEdit 4.0pre1
1021   */
1022  public void getLineText(int line, Segment segment)
1023  {
1024    if(line < 0 || line >= lineMgr.getLineCount())
1025      throw new ArrayIndexOutOfBoundsException(line);
1026
1027    try
1028    {
1029      readLock();
1030
1031      int start = (line == 0 ? 0
1032        : lineMgr.getLineEndOffset(line - 1));
1033      int end = lineMgr.getLineEndOffset(line);
1034
1035      getText(start,end - start - 1,segment);
1036    }
1037    finally
1038    {
1039      readUnlock();
1040    }
1041  } //}}}
1042
1043  //{{{ getText() method
1044  /**
1045   * Returns the specified text range. This method is thread-safe.
1046   * @param start The start offset
1047   * @param length The number of characters to get
1048   */
1049  public String getText(int start, int length)
1050  {
1051    try
1052    {
1053      readLock();
1054
1055      if(start < 0 || length < 0
1056        || start + length > contentMgr.getLength())
1057        throw new ArrayIndexOutOfBoundsException(start + ":" + length);
1058
1059      return contentMgr.getText(start,length);
1060    }
1061    finally
1062    {
1063      readUnlock();
1064    }
1065  } //}}}
1066
1067  //{{{ getText() method
1068  /**
1069   * Returns the specified text range in a <code>Segment</code>.<p>
1070   *
1071   * Using a <classname>Segment</classname> is generally more
1072   * efficient than using a <classname>String</classname> because it
1073   * results in less memory allocation and array copying.<p>
1074   *
1075   * This method is thread-safe.
1076   *
1077   * @param start The start offset
1078   * @param length The number of characters to get
1079   * @param seg The segment to copy the text to
1080   */
1081  public void getText(int start, int length, Segment seg)
1082  {
1083    try
1084    {
1085      readLock();
1086
1087      if(start < 0 || length < 0
1088        || start + length > contentMgr.getLength())
1089        throw new ArrayIndexOutOfBoundsException(start + ":" + length);
1090
1091      contentMgr.getText(start,length,seg);
1092    }
1093    finally
1094    {
1095      readUnlock();
1096    }
1097  } //}}}
1098
1099  //{{{ insert() method
1100  /**
1101   * Inserts a string into the buffer.
1102   * @param offset The offset
1103   * @param str The string
1104   * @since jEdit 4.0pre1
1105   */
1106  public void insert(int offset, String str)
1107  {
1108    if(str == null)
1109      return;
1110
1111    int len = str.length();
1112
1113    if(len == 0)
1114      return;
1115
1116    if(isReadOnly())
1117      throw new RuntimeException("buffer read-only");
1118
1119    try
1120    {
1121      writeLock();
1122
1123      if(offset < 0 || offset > contentMgr.getLength())
1124        throw new ArrayIndexOutOfBoundsException(offset);
1125
1126      contentMgr.insert(offset,str);
1127
1128      integerArray.clear();
1129
1130      for(int i = 0; i < len; i++)
1131      {
1132        if(str.charAt(i) == '\n')
1133          integerArray.add(i + 1);
1134      }
1135
1136      if(!getFlag(UNDO_IN_PROGRESS))
1137      {
1138        undoMgr.contentInserted(offset,len,str,
1139          !getFlag(DIRTY));
1140      }
1141
1142      contentInserted(offset,len,integerArray);
1143    }
1144    finally
1145    {
1146      writeUnlock();
1147    }
1148  } //}}}
1149
1150  //{{{ insert() method
1151  /**
1152   * Inserts a string into the buffer.
1153   * @param offset The offset
1154   * @param seg The segment
1155   * @since jEdit 4.0pre1
1156   */
1157  public void insert(int offset, Segment seg)
1158  {
1159    if(seg.count == 0)
1160      return;
1161
1162    if(isReadOnly())
1163      throw new RuntimeException("buffer read-only");
1164
1165    try
1166    {
1167      writeLock();
1168
1169      if(offset < 0 || offset > contentMgr.getLength())
1170        throw new ArrayIndexOutOfBoundsException(offset);
1171
1172      contentMgr.insert(offset,seg);
1173
1174      integerArray.clear();
1175
1176      for(int i = 0; i < seg.count; i++)
1177      {
1178        if(seg.array[seg.offset + i] == '\n')
1179          integerArray.add(i + 1);
1180      }
1181
1182      if(!getFlag(UNDO_IN_PROGRESS))
1183      {
1184        undoMgr.contentInserted(offset,seg.count,
1185          seg.toString(),!getFlag(DIRTY));
1186      }
1187
1188      contentInserted(offset,seg.count,integerArray);
1189    }
1190    finally
1191    {
1192      writeUnlock();
1193    }
1194  } //}}}
1195
1196  //{{{ remove() method
1197  /**
1198   * Removes the specified rang efrom the buffer.
1199   * @param offset The start offset
1200   * @param length The number of characters to remove
1201   */
1202  public void remove(int offset, int length)
1203  {
1204    if(length == 0)
1205      return;
1206
1207    if(isReadOnly())
1208      throw new RuntimeException("buffer read-only");
1209
1210    try
1211    {
1212      setFlag(TRANSACTION,true);
1213      writeLock();
1214
1215      if(offset < 0 || length < 0
1216        || offset + length > contentMgr.getLength())
1217        throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
1218
1219      int startLine = lineMgr.getLineOfOffset(offset);
1220      int endLine = lineMgr.getLineOfOffset(offset + length);
1221
1222      int numLines = endLine - startLine;
1223
1224      if(!getFlag(UNDO_IN_PROGRESS) && !getFlag(LOADING))
1225      {
1226        undoMgr.contentRemoved(offset,length,
1227          getText(offset,length),
1228          !getFlag(DIRTY));
1229      }
1230
1231      firePreContentRemoved(startLine,offset,numLines,length);
1232
1233      contentMgr.remove(offset,length);
1234      lineMgr.contentRemoved(startLine,offset,numLines,length);
1235      positionMgr.contentRemoved(offset,length);
1236
1237      fireContentRemoved(startLine,offset,numLines,length);
1238
1239      /* otherwise it will be delivered later */
1240      if(!getFlag(UNDO_IN_PROGRESS) && !insideCompoundEdit())
1241        fireTransactionComplete();
1242
1243      setDirty(true);
1244    }
1245    finally
1246    {
1247      setFlag(TRANSACTION,false);
1248      writeUnlock();
1249    }
1250  } //}}}
1251
1252  //}}}
1253
1254  //{{{ Undo
1255
1256  //{{{ undo() method
1257  /**
1258   * Undoes the most recent edit.
1259   *
1260   * @since jEdit 4.0pre1
1261   */
1262  public void undo(JEditTextArea textArea)
1263  {
1264    if(undoMgr == null)
1265      return;
1266
1267    if(!isEditable())
1268    {
1269      textArea.getToolkit().beep();
1270      return;
1271    }
1272
1273    try
1274    {
1275      writeLock();
1276
1277      setFlag(UNDO_IN_PROGRESS,true);
1278      int caret = undoMgr.undo();
1279
1280      if(caret == -1)
1281        textArea.getToolkit().beep();
1282      else
1283        textArea.setCaretPosition(caret);
1284
1285      fireTransactionComplete();
1286    }
1287    finally
1288    {
1289      setFlag(UNDO_IN_PROGRESS,false);
1290
1291      writeUnlock();
1292    }
1293  } //}}}
1294
1295  //{{{ redo() method
1296  /**
1297   * Redoes the most recently undone edit.
1298   *
1299   * @since jEdit 2.7pre2
1300   */
1301  public void redo(JEditTextArea textArea)
1302  {
1303    if(undoMgr == null)
1304      return;
1305
1306    if(!isEditable())
1307    {
1308      Toolkit.getDefaultToolkit().beep();
1309      return;
1310    }
1311
1312    try
1313    {
1314      writeLock();
1315
1316      setFlag(UNDO_IN_PROGRESS,true);
1317      int caret = undoMgr.redo();
1318      if(caret == -1)
1319        textArea.getToolkit().beep();
1320      else
1321        textArea.setCaretPosition(caret);
1322
1323      fireTransactionComplete();
1324    }
1325    finally
1326    {
1327      setFlag(UNDO_IN_PROGRESS,false);
1328
1329      writeUnlock();
1330    }
1331  } //}}}
1332
1333  //{{{ isTransactionInProgress() method
1334  /**
1335   * Returns if an undo or compound edit is currently in progress. If this
1336   * method returns true, then eventually a
1337   * {@link org.gjt.sp.jedit.buffer.BufferChangeListener#transactionComplete(Buffer)}
1338   * buffer event will get fired.
1339   * @since jEdit 4.0pre6
1340   */
1341  public boolean isTransactionInProgress()
1342  {
1343    return getFlag(TRANSACTION)
1344      || getFlag(UNDO_IN_PROGRESS)
1345      || insideCompoundEdit();
1346  } //}}}
1347
1348  //{{{ beginCompoundEdit() method
1349  /**
1350   * Starts a compound edit. All edits from now on until
1351   * {@link #endCompoundEdit()} are called will be merged
1352   * into one. This can be used to make a complex operation
1353   * undoable in one step. Nested calls to
1354   * {@link #beginCompoundEdit()} behave as expected,
1355   * requiring the same number of {@link #endCompoundEdit()}
1356   * calls to end the edit.
1357   * @see #endCompoundEdit()
1358   */
1359  public void beginCompoundEdit()
1360  {
1361    // Why?
1362    //if(getFlag(TEMPORARY))
1363    //  return;
1364
1365    try
1366    {
1367      writeLock();
1368
1369      undoMgr.beginCompoundEdit();
1370    }
1371    finally
1372    {
1373      writeUnlock();
1374    }
1375  } //}}}
1376
1377  //{{{ endCompoundEdit() method
1378  /**
1379   * Ends a compound edit. All edits performed since
1380   * {@link #beginCompoundEdit()} was called can now
1381   * be undone in one step by calling {@link #undo(JEditTextArea)}.
1382   * @see #beginCompoundEdit()
1383   */
1384  public void endCompoundEdit()
1385  {
1386    // Why?
1387    //if(getFlag(TEMPORARY))
1388    //  return;
1389
1390    try
1391    {
1392      writeLock();
1393
1394      undoMgr.endCompoundEdit();
1395
1396      if(!insideCompoundEdit())
1397        fireTransactionComplete();
1398    }
1399    finally
1400    {
1401      writeUnlock();
1402    }
1403  }//}}}
1404
1405  //{{{ insideCompoundEdit() method
1406  /**
1407   * Returns if a compound edit is currently active.
1408   * @since jEdit 3.1pre1
1409   */
1410  public boolean insideCompoundEdit()
1411  {
1412    return undoMgr.insideCompoundEdit();
1413  } //}}}
1414
1415  //}}}
1416
1417  //{{{ Buffer events
1418  public static final int NORMAL_PRIORITY = 0;
1419  public static final int HIGH_PRIORITY = 1;
1420  static class Listener
1421  {
1422    BufferChangeListener listener;
1423    int priority;
1424
1425    Listener(BufferChangeListener listener, int priority)
1426    {
1427      this.listener = listener;
1428      this.priority = priority;
1429    }
1430  }
1431
1432  //{{{ addBufferChangeListener() method
1433  /**
1434   * Adds a buffer change listener.
1435   * @param listener The listener
1436   * @param priority Listeners with HIGH_PRIORITY get the event before
1437   * listeners with NORMAL_PRIORITY
1438   * @since jEdit 4.2pre2
1439   */
1440  public void addBufferChangeListener(BufferChangeListener listener,
1441    int priority)
1442  {
1443    Listener l = new Listener(listener,priority);
1444    for(int i = 0; i < bufferListeners.size(); i++)
1445    {
1446      Listener _l = (Listener)bufferListeners.get(i);
1447      if(_l.priority < priority)
1448      {
1449        bufferListeners.insertElementAt(l,i);
1450        return;
1451      }
1452    }
1453    bufferListeners.addElement(l);
1454  } //}}}
1455
1456  //{{{ addBufferChangeListener() method
1457  /**
1458   * Adds a buffer change listener.
1459   * @param listener The listener
1460   * @since jEdit 4.0pre1
1461   */
1462  public void addBufferChangeListener(BufferChangeListener listener)
1463  {
1464    addBufferChangeListener(listener,NORMAL_PRIORITY);
1465  } //}}}
1466
1467  //{{{ removeBufferChangeListener() method
1468  /**
1469   * Removes a buffer change listener.
1470   * @param listener The listener
1471   * @since jEdit 4.0pre1
1472   */
1473  public void removeBufferChangeListener(BufferChangeListener listener)
1474  {
1475    for(int i = 0; i < bufferListeners.size(); i++)
1476    {
1477      if(((Listener)bufferListeners.get(i)).listener == listener)
1478      {
1479        bufferListeners.removeElementAt(i);
1480        return;
1481      }
1482    }
1483  } //}}}
1484
1485  //{{{ getBufferChangeListeners() method
1486  /**
1487   * Returns an array of registered buffer change listeners.
1488   * @param listener The listener
1489   * @since jEdit 4.1pre3
1490   */
1491  public BufferChangeListener[] getBufferChangeListeners()
1492  {
1493    BufferChangeListener[] returnValue
1494      = new BufferChangeListener[
1495      bufferListeners.size()];
1496    for(int i = 0; i < returnValue.length; i++)
1497    {
1498      returnValue[i] = ((Listener)bufferListeners.get(i))
1499        .listener;
1500    }
1501    return returnValue;
1502  } //}}}
1503
1504  //}}}
1505
1506  //{{{ Property methods
1507
1508  //{{{ propertiesChanged() method
1509  /**
1510   * Reloads settings from the properties. This should be called
1511   * after the <code>syntax</code> or <code>folding</code>
1512   * buffer-local properties are changed.
1513   */
1514  public void propertiesChanged()
1515  {
1516    String folding = getStringProperty("folding");
1517    FoldHandler handler = FoldHandler.getFoldHandler(folding);
1518
1519    if(handler != null)
1520    {
1521      setFoldHandler(handler);
1522    }
1523    else
1524    {
1525      if (folding != null)
1526        Log.log(Log.WARNING, this, path + ": invalid 'folding' property: " + folding); 
1527      setFoldHandler(new DummyFoldHandler());
1528    }
1529
1530    EditBus.send(new BufferUpdate(this,null,BufferUpdate.PROPERTIES_CHANGED));
1531  } //}}}
1532
1533  //{{{ getTabSize() method
1534  /**
1535   * Returns the tab size used in this buffer. This is equivalent
1536   * to calling <code>getProperty("tabSize")</code>.
1537   * This method is thread-safe.
1538   */
1539  public int getTabSize()
1540  {
1541    return getIntegerProperty("tabSize",8);
1542  } //}}}
1543
1544  //{{{ getIndentSize() method
1545  /**
1546   * Returns the indent size used in this buffer. This is equivalent
1547   * to calling <code>getProperty("indentSize")</code>.
1548   * This method is thread-safe.
1549   * @since jEdit 2.7pre1
1550   */
1551  public int getIndentSize()
1552  {
1553    return getIntegerProperty("indentSize",8);
1554  } //}}}
1555
1556  //{{{ getProperty() method
1557  /**
1558   * Returns the value of a buffer-local property.<p>
1559   *
1560   * Using this method is generally discouraged, because it returns an
1561   * <code>Object</code> which must be cast to another type
1562   * in order to be useful, and this can cause problems if the object
1563   * is of a different type than what the caller expects.<p>
1564   *
1565   * The following methods should be used instead:
1566   * <ul>
1567   * <li>{@link #getStringProperty(String)}</li>
1568   * <li>{@link #getBooleanProperty(String)}</li>
1569   * <li>{@link #getIntegerProperty(String,int)}</li>
1570   * <li>{@link #getRegexpProperty(String,int,gnu.regexp.RESyntax)}</li>
1571   * </ul>
1572   *
1573   * This method is thread-safe.
1574   *
1575   * @param name The property name. For backwards compatibility, this
1576   * is an <code>Object</code>, not a <code>String</code>.
1577   */
1578  public Object getProperty(Object name)
1579  {
1580    synchronized(propertyLock)
1581    {
1582      // First try the buffer-local properties
1583      PropValue o = (PropValue)properties.get(name);
1584      if(o != null)
1585        return o.value;
1586
1587      // For backwards compatibility
1588      if(!(name instanceof String))
1589        return null;
1590
1591      // Now try mode.<mode>.<property>
1592      if(mode != null)
1593      {
1594        Object retVal = mode.getProperty((String)name);
1595        if(retVal == null)
1596          return null;
1597
1598        properties.put(name,new PropValue(retVal,true));
1599        return retVal;
1600      }
1601      else
1602      {
1603        // Now try buffer.<property>
1604        String value = jEdit.getProperty("buffer." + name);
1605        if(value == null)
1606          return null;
1607
1608        // Try returning it as an integer first
1609        Object retVal;
1610        try
1611        {
1612          retVal = new Integer(value);
1613        }
1614        catch(NumberFormatException nf)
1615        {
1616          retVal = value;
1617        }
1618        properties.put(name,new PropValue(retVal,true));
1619        return retVal;
1620      }
1621    }
1622  } //}}}
1623
1624  //{{{ setProperty() method
1625  /**
1626   * Sets the value of a buffer-local property.
1627   * @param name The property name
1628   * @param value The property value
1629   * @since jEdit 4.0pre1
1630   */
1631  public void setProperty(String name, Object value)
1632  {
1633    if(value == null)
1634      properties.remove(name);
1635    else
1636    {
1637      PropValue test = (PropValue)properties.get(name);
1638      if(test == null)
1639        properties.put(name,new PropValue(value,false));
1640      else if(test.value.equals(value))
1641      {
1642        // do nothing
1643      }
1644      else
1645      {
1646        test.value = value;
1647        test.defaultValue = false;
1648      }
1649    }
1650  } //}}}
1651
1652  //{{{ unsetProperty() method
1653  /**
1654   * Clears the value of a buffer-local property.
1655   * @param name The property name
1656   * @since jEdit 4.0pre1
1657   */
1658  public void unsetProperty(String name)
1659  {
1660    properties.remove(name);
1661  } //}}}
1662
1663  //{{{ getStringProperty() method
1664  /**
1665   * Returns the value of a string property. This method is thread-safe.
1666   * @param name The property name
1667   * @since jEdit 4.0pre1
1668   */
1669  public String getStringProperty(String name)
1670  {
1671    Object obj = getProperty(name);
1672    if(obj != null)
1673