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