KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > org > gjt > sp > jedit > buffer > JEditBuffer


1 /*
2  * JEditBuffer.java - jEdit buffer
3  * :tabSize=8:indentSize=8:noTabs=false:
4  * :folding=explicit:collapseFolds=1:
5  *
6  * Copyright (C) 1998, 2005 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.buffer;
25
26 //{{{ Imports
27
import javax.swing.*;
28 import javax.swing.text.*;
29 import java.awt.Toolkit JavaDoc;
30 import java.lang.reflect.*;
31 import java.util.*;
32 import java.util.regex.Pattern JavaDoc;
33 import org.gjt.sp.jedit.jEdit;
34 import org.gjt.sp.jedit.Debug;
35 import org.gjt.sp.jedit.Mode;
36 import org.gjt.sp.jedit.TextUtilities;
37 import org.gjt.sp.jedit.indent.*;
38 import org.gjt.sp.jedit.syntax.*;
39 import org.gjt.sp.jedit.textarea.*;
40 import org.gjt.sp.util.*;
41 //}}}
42

43 /**
44  * A <code>JEditBuffer</code> represents the contents of an open text
45  * file as it is maintained in the computer's memory (as opposed to
46  * how it may be stored on a disk).<p>
47  *
48  * This class is partially thread-safe, however you must pay attention to two
49  * very important guidelines:
50  * <ul>
51  * <li>Changes to a buffer can only be made from the AWT thread.
52  * <li>When accessing the buffer from another thread, you must
53  * grab a read lock if you plan on performing more than one call, to ensure that
54  * the buffer contents are not changed by the AWT thread for the duration of the
55  * lock. Only methods whose descriptions specify thread safety can be invoked
56  * from other threads.
57  * </ul>
58  *
59  * @author Slava Pestov
60  * @version $Id: JEditBuffer.java 8301 2007-01-02 17:22:36Z ezust $
61  *
62  * @since jEdit 4.3pre3
63  */

64 public class JEditBuffer
65 {
66     /**
67      * Line separator property.
68      */

69     public static final String JavaDoc LINESEP = "lineSeparator";
70
71     /**
72      * Character encoding used when loading and saving.
73      * @since jEdit 3.2pre4
74      */

75     public static final String JavaDoc ENCODING = "encoding";
76
77     //{{{ JEditBuffer constructor
78
public JEditBuffer(Map props)
79     {
80         bufferListeners = new Vector<Listener>();
81         lock = new ReadWriteLock();
82         contentMgr = new ContentManager();
83         lineMgr = new LineManager();
84         positionMgr = new PositionManager(this);
85         undoMgr = new UndoManager(this);
86         seg = new Segment();
87         integerArray = new IntegerArray();
88         propertyLock = new Object JavaDoc();
89         properties = new HashMap<Object JavaDoc, PropValue>();
90
91         //{{{ need to convert entries of 'props' to PropValue instances
92
Set<Map.Entry> set = props.entrySet();
93         for (Map.Entry entry : set)
94         {
95             properties.put(entry.getKey(),new PropValue(entry.getValue(),false));
96         } //}}}
97

98         // fill in defaults for these from system properties if the
99
// corresponding buffer.XXX properties not set
100
if(getProperty(ENCODING) == null)
101             properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
102         if(getProperty(LINESEP) == null)
103             properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
104     } //}}}
105

106     //{{{ JEditBuffer constructor
107
public JEditBuffer()
108     {
109         bufferListeners = new Vector<Listener>();
110         lock = new ReadWriteLock();
111         contentMgr = new ContentManager();
112         lineMgr = new LineManager();
113         positionMgr = new PositionManager(this);
114         undoMgr = new UndoManager(this);
115         seg = new Segment();
116         integerArray = new IntegerArray();
117         propertyLock = new Object JavaDoc();
118         properties = new HashMap<Object JavaDoc, PropValue>();
119
120         properties.put("wrap",new PropValue("none",false));
121
122         TokenMarker tokenMarker = new TokenMarker();
123         tokenMarker.addRuleSet(new ParserRuleSet("text","MAIN"));
124         setTokenMarker(tokenMarker);
125
126         loadText(null,null);
127         // corresponding buffer.XXX properties not set
128
if(getProperty(ENCODING) == null)
129             properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
130         if(getProperty(LINESEP) == null)
131             properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
132
133         setFoldHandler(new DummyFoldHandler());
134     } //}}}
135

136     //{{{ Flags
137

138     //{{{ isDirty() method
139
/**
140      * Returns whether there have been unsaved changes to this buffer.
141      * This method is thread-safe.
142      */

143     public boolean isDirty()
144     {
145         return dirty;
146     } //}}}
147

148     //{{{ isLoading() method
149
public boolean isLoading()
150     {
151         return loading;
152     } //}}}
153

154     //{{{ setLoading() method
155
public void setLoading(boolean loading)
156     {
157         this.loading = loading;
158     } //}}}
159

160     //{{{ isPerformingIO() method
161
/**
162      * Returns true if the buffer is currently performing I/O.
163      * This method is thread-safe.
164      * @since jEdit 2.7pre1
165      */

166     public boolean isPerformingIO()
167     {
168         return isLoading() || io;
169     } //}}}
170

171     //{{{ setPerformingIO() method
172
/**
173      * Returns true if the buffer is currently performing I/O.
174      * This method is thread-safe.
175      * @since jEdit 2.7pre1
176      */

177     public void setPerformingIO(boolean io)
178     {
179         this.io = io;
180     } //}}}
181

182     //{{{ isEditable() method
183
/**
184      * Returns true if this file is editable, false otherwise. A file may
185      * become uneditable if it is read only, or if I/O is in progress.
186      * This method is thread-safe.
187      * @since jEdit 2.7pre1
188      */

189     public boolean isEditable()
190     {
191         return !(isReadOnly() || isPerformingIO());
192     } //}}}
193

194     //{{{ isReadOnly() method
195
/**
196      * Returns true if this file is read only, false otherwise.
197      * This method is thread-safe.
198      */

199     public boolean isReadOnly()
200     {
201         return readOnly || readOnlyOverride;
202     } //}}}
203

204     //{{{ setReadOnly() method
205
/**
206      * Sets the read only flag.
207      * @param readOnly The read only flag
208      */

209     public void setReadOnly(boolean readOnly)
210     {
211         readOnlyOverride = readOnly;
212     } //}}}
213

214     //{{{ setDirty() method
215
/**
216      * Sets the 'dirty' (changed since last save) flag of this buffer.
217      */

218     public void setDirty(boolean d)
219     {
220         boolean editable = isEditable();
221
222         if(d)
223         {
224             if(editable)
225                 dirty = true;
226         }
227         else
228         {
229             dirty = false;
230
231             // fixes dirty flag not being reset on
232
// save/insert/undo/redo/undo
233
if(!isUndoInProgress())
234             {
235                 // this ensures that undo can clear the dirty flag properly
236
// when all edits up to a save are undone
237
undoMgr.bufferSaved();
238             }
239         }
240     } //}}}
241

242     //}}}
243

244     //{{{ Thread safety
245

246     //{{{ readLock() method
247
/**
248      * The buffer is guaranteed not to change between calls to
249      * {@link #readLock()} and {@link #readUnlock()}.
250      */

251     public void readLock()
252     {
253         lock.readLock();
254     } //}}}
255

256     //{{{ readUnlock() method
257
/**
258      * The buffer is guaranteed not to change between calls to
259      * {@link #readLock()} and {@link #readUnlock()}.
260      */

261     public void readUnlock()
262     {
263         lock.readUnlock();
264     } //}}}
265

266     //{{{ writeLock() method
267
/**
268      * Attempting to obtain read lock will block between calls to
269      * {@link #writeLock()} and {@link #writeUnlock()}.
270      */

271     public void writeLock()
272     {
273         lock.writeLock();
274     } //}}}
275

276     //{{{ writeUnlock() method
277
/**
278      * Attempting to obtain read lock will block between calls to
279      * {@link #writeLock()} and {@link #writeUnlock()}.
280      */

281     public void writeUnlock()
282     {
283         lock.writeUnlock();
284     } //}}}
285

286     //}}}
287

288     //{{{ Line offset methods
289

290     //{{{ getLength() method
291
/**
292      * Returns the number of characters in the buffer. This method is thread-safe.
293      */

294     public int getLength()
295     {
296         // no need to lock since this just returns a value and that's it
297
return contentMgr.getLength();
298     } //}}}
299

300     //{{{ getLineCount() method
301
/**
302      * Returns the number of physical lines in the buffer.
303      * This method is thread-safe.
304      * @since jEdit 3.1pre1
305      */

306     public int getLineCount()
307     {
308         // no need to lock since this just returns a value and that's it
309
return lineMgr.getLineCount();
310     } //}}}
311

312     //{{{ getLineOfOffset() method
313
/**
314      * Returns the line containing the specified offset.
315      * This method is thread-safe.
316      * @param offset The offset
317      * @since jEdit 4.0pre1
318      */

319     public int getLineOfOffset(int offset)
320     {
321         try
322         {
323             readLock();
324
325             if(offset < 0 || offset > getLength())
326                 throw new ArrayIndexOutOfBoundsException JavaDoc(offset);
327
328             return lineMgr.getLineOfOffset(offset);
329         }
330         finally
331         {
332             readUnlock();
333         }
334     } //}}}
335

336     //{{{ getLineStartOffset() method
337
/**
338      * Returns the start offset of the specified line.
339      * This method is thread-safe.
340      * @param line The line
341      * @return The start offset of the specified line
342      * @since jEdit 4.0pre1
343      */

344     public int getLineStartOffset(int line)
345     {
346         try
347         {
348             readLock();
349
350             if(line < 0 || line >= lineMgr.getLineCount())
351                 throw new ArrayIndexOutOfBoundsException JavaDoc(line);
352             else if(line == 0)
353                 return 0;
354
355             return lineMgr.getLineEndOffset(line - 1);
356         }
357         finally
358         {
359             readUnlock();
360         }
361     } //}}}
362

363     //{{{ getLineEndOffset() method
364
/**
365      * Returns the end offset of the specified line.
366      * This method is thread-safe.
367      * @param line The line
368      * @return The end offset of the specified line
369      * invalid.
370      * @since jEdit 4.0pre1
371      */

372     public int getLineEndOffset(int line)
373     {
374         try
375         {
376             readLock();
377
378             if(line < 0 || line >= lineMgr.getLineCount())
379                 throw new ArrayIndexOutOfBoundsException JavaDoc(line);
380
381             return lineMgr.getLineEndOffset(line);
382         }
383         finally
384         {
385             readUnlock();
386         }
387     } //}}}
388

389     //{{{ getLineLength() method
390
/**
391      * Returns the length of the specified line.
392      * This method is thread-safe.
393      * @param line The line
394      * @since jEdit 4.0pre1
395      */

396     public int getLineLength(int line)
397     {
398         try
399         {
400             readLock();
401
402             return getLineEndOffset(line)
403                 - getLineStartOffset(line) - 1;
404         }
405         finally
406         {
407             readUnlock();
408         }
409     } //}}}
410

411     //{{{ getPriorNonEmptyLine() method
412
/**
413      * Auto indent needs this.
414      */

415     public int getPriorNonEmptyLine(int lineIndex)
416     {
417         int returnValue = -1;
418
419         for(int i = lineIndex - 1; i >= 0; i--)
420         {
421             getLineText(i,seg);
422             if(seg.count != 0)
423                 returnValue = i;
424             for(int j = 0; j < seg.count; j++)
425             {
426                 char ch = seg.array[seg.offset + j];
427                 if(!Character.isWhitespace(ch))
428                     return i;
429             }
430         }
431
432         // didn't find a line that contains non-whitespace chars
433
// so return index of prior whitespace line
434
return returnValue;
435     } //}}}
436

437     //}}}
438

439     //{{{ Text getters and setters
440

441     //{{{ getLineText() method
442
/**
443      * Returns the text on the specified line.
444      * This method is thread-safe.
445      * @param line The line
446      * @return The text, or null if the line is invalid
447      * @since jEdit 4.0pre1
448      */

449     public String JavaDoc getLineText(int line)
450     {
451         if(line < 0 || line >= lineMgr.getLineCount())
452             throw new ArrayIndexOutOfBoundsException JavaDoc(line);
453
454         try
455         {
456             readLock();
457
458             int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
459             int end = lineMgr.getLineEndOffset(line);
460
461             return getText(start,end - start - 1);
462         }
463         finally
464         {
465             readUnlock();
466         }
467     } //}}}
468

469     //{{{ getLineText() method
470
/**
471      * Returns the specified line in a <code>Segment</code>.<p>
472      *
473      * Using a <classname>Segment</classname> is generally more
474      * efficient than using a <classname>String</classname> because it
475      * results in less memory allocation and array copying.<p>
476      *
477      * This method is thread-safe.
478      *
479      * @param line The line
480      * @since jEdit 4.0pre1
481      */

482     public void getLineText(int line, Segment segment)
483     {
484         if(line < 0 || line >= lineMgr.getLineCount())
485             throw new ArrayIndexOutOfBoundsException JavaDoc(line);
486
487         try
488         {
489             readLock();
490
491             int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
492             int end = lineMgr.getLineEndOffset(line);
493
494             getText(start,end - start - 1,segment);
495         }
496         finally
497         {
498             readUnlock();
499         }
500     } //}}}
501

502     //{{{ getText() method
503
/**
504      * Returns the specified text range. This method is thread-safe.
505      * @param start The start offset
506      * @param length The number of characters to get
507      */

508     public String JavaDoc getText(int start, int length)
509     {
510         try
511         {
512             readLock();
513
514             if(start < 0 || length < 0
515                 || start + length > contentMgr.getLength())
516                 throw new ArrayIndexOutOfBoundsException JavaDoc(start + ":" + length);
517
518             return contentMgr.getText(start,length);
519         }
520         finally
521         {
522             readUnlock();
523         }
524     } //}}}
525

526     //{{{ getText() method
527
/**
528      * Returns the specified text range in a <code>Segment</code>.<p>
529      *
530      * Using a <classname>Segment</classname> is generally more
531      * efficient than using a <classname>String</classname> because it
532      * results in less memory allocation and array copying.<p>
533      *
534      * This method is thread-safe.
535      *
536      * @param start The start offset
537      * @param length The number of characters to get
538      * @param seg The segment to copy the text to
539      */

540     public void getText(int start, int length, Segment seg)
541     {
542         try
543         {
544             readLock();
545
546             if(start < 0 || length < 0
547                 || start + length > contentMgr.getLength())
548                 throw new ArrayIndexOutOfBoundsException JavaDoc(start + ":" + length);
549
550             contentMgr.getText(start,length,seg);
551         }
552         finally
553         {
554             readUnlock();
555         }
556     } //}}}
557

558     //{{{ insert() method
559
/**
560      * Inserts a string into the buffer.
561      * @param offset The offset
562      * @param str The string
563      * @since jEdit 4.0pre1
564      */

565     public void insert(int offset, String JavaDoc str)
566     {
567         if(str == null)
568             return;
569
570         int len = str.length();
571
572         if(len == 0)
573             return;
574
575         if(isReadOnly())
576             throw new RuntimeException JavaDoc("buffer read-only");
577
578         try
579         {
580             writeLock();
581
582             if(offset < 0 || offset > contentMgr.getLength())
583                 throw new ArrayIndexOutOfBoundsException JavaDoc(offset);
584
585             contentMgr.insert(offset,str);
586
587             integerArray.clear();
588
589             for(int i = 0; i < len; i++)
590             {
591                 if(str.charAt(i) == '\n')
592                     integerArray.add(i + 1);
593             }
594
595             if(!undoInProgress)
596             {
597                 undoMgr.contentInserted(offset,len,str,!dirty);
598             }
599
600             contentInserted(offset,len,integerArray);
601         }
602         finally
603         {
604             writeUnlock();
605         }
606     } //}}}
607

608     //{{{ insert() method
609
/**
610      * Inserts a string into the buffer.
611      * @param offset The offset
612      * @param seg The segment
613      * @since jEdit 4.0pre1
614      */

615     public void insert(int offset, Segment seg)
616     {
617         if(seg.count == 0)
618             return;
619
620         if(isReadOnly())
621             throw new RuntimeException JavaDoc("buffer read-only");
622
623         try
624         {
625             writeLock();
626
627             if(offset < 0 || offset > contentMgr.getLength())
628                 throw new ArrayIndexOutOfBoundsException JavaDoc(offset);
629
630             contentMgr.insert(offset,seg);
631
632             integerArray.clear();
633
634             for(int i = 0; i < seg.count; i++)
635             {
636                 if(seg.array[seg.offset + i] == '\n')
637                     integerArray.add(i + 1);
638             }
639
640             if(!undoInProgress)
641             {
642                 undoMgr.contentInserted(offset,seg.count,
643                     seg.toString(),!dirty);
644             }
645
646             contentInserted(offset,seg.count,integerArray);
647         }
648         finally
649         {
650             writeUnlock();
651         }
652     } //}}}
653

654     //{{{ remove() method
655
/**
656      * Removes the specified rang efrom the buffer.
657      * @param offset The start offset
658      * @param length The number of characters to remove
659      */

660     public void remove(int offset, int length)
661     {
662         if(length == 0)
663             return;
664
665         if(isReadOnly())
666             throw new RuntimeException JavaDoc("buffer read-only");
667
668         try
669         {
670             transaction = true;
671
672             writeLock();
673
674             if(offset < 0 || length < 0
675                 || offset + length > contentMgr.getLength())
676                 throw new ArrayIndexOutOfBoundsException JavaDoc(offset + ":" + length);
677
678             int startLine = lineMgr.getLineOfOffset(offset);
679             int endLine = lineMgr.getLineOfOffset(offset + length);
680
681             int numLines = endLine - startLine;
682
683             if(!undoInProgress && !loading)
684             {
685                 undoMgr.contentRemoved(offset,length,
686                     getText(offset,length),
687                     !dirty);
688             }
689
690             firePreContentRemoved(startLine,offset,numLines,length);
691
692             contentMgr.remove(offset,length);
693             lineMgr.contentRemoved(startLine,offset,numLines,length);
694             positionMgr.contentRemoved(offset,length);
695
696             fireContentRemoved(startLine,offset,numLines,length);
697
698             /* otherwise it will be delivered later */
699             if(!undoInProgress && !insideCompoundEdit())
700                 fireTransactionComplete();
701
702             setDirty(true);
703         }
704         finally
705         {
706             transaction = false;
707
708             writeUnlock();
709         }
710     } //}}}
711

712     //}}}
713

714     //{{{ Indentation
715

716     //{{{ removeTrailingWhiteSpace() method
717
/**
718      * Removes trailing whitespace from all lines in the specified list.
719      * @param lines The line numbers
720      * @since jEdit 3.2pre1
721      */

722     public void removeTrailingWhiteSpace(int[] lines)
723     {
724         try
725         {
726             beginCompoundEdit();
727
728             for(int i = 0; i < lines.length; i++)
729             {
730                 int pos, lineStart, lineEnd, tail;
731
732                 getLineText(lines[i],seg);
733
734                 // blank line
735
if (seg.count == 0) continue;
736
737                 lineStart = seg.offset;
738                 lineEnd = seg.offset + seg.count - 1;
739
740                 for (pos = lineEnd; pos >= lineStart; pos--)
741                 {
742                     if (!Character.isWhitespace(seg.array[pos]))
743                         break;
744                 }
745
746                 tail = lineEnd - pos;
747
748                 // no whitespace
749
if (tail == 0) continue;
750
751                 remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
752             }
753         }
754         finally
755         {
756             endCompoundEdit();
757         }
758     } //}}}
759

760     //{{{ shiftIndentLeft() method
761
/**
762      * Shifts the indent of each line in the specified list to the left.
763      * @param lines The line numbers
764      * @since jEdit 3.2pre1
765      */

766     public void shiftIndentLeft(int[] lines)
767     {
768         int tabSize = getTabSize();
769         int indentSize = getIndentSize();
770         boolean noTabs = getBooleanProperty("noTabs");
771
772         try
773         {
774             beginCompoundEdit();
775
776             for(int i = 0; i < lines.length; i++)
777             {
778                 int lineStart = getLineStartOffset(lines[i]);
779                 String JavaDoc line = getLineText(lines[i]);
780                 int whiteSpace = StandardUtilities
781                     .getLeadingWhiteSpace(line);
782                 if(whiteSpace == 0)
783                     continue;
784                 int whiteSpaceWidth = Math.max(0,StandardUtilities
785                     .getLeadingWhiteSpaceWidth(line,tabSize)
786                     - indentSize);
787
788                 insert(lineStart + whiteSpace,StandardUtilities
789                     .createWhiteSpace(whiteSpaceWidth,
790                     noTabs ? 0 : tabSize));
791                 remove(lineStart,whiteSpace);
792             }
793
794         }
795         finally
796         {
797             endCompoundEdit();
798         }
799     } //}}}
800

801     //{{{ shiftIndentRight() method
802
/**
803      * Shifts the indent of each line in the specified list to the right.
804      * @param lines The line numbers
805      * @since jEdit 3.2pre1
806      */

807     public void shiftIndentRight(int[] lines)
808     {
809         try
810         {
811             beginCompoundEdit();
812
813             int tabSize = getTabSize();
814             int indentSize = getIndentSize();
815             boolean noTabs = getBooleanProperty("noTabs");
816             for(int i = 0; i < lines.length; i++)
817             {
818                 int lineStart = getLineStartOffset(lines[i]);
819                 String JavaDoc line = getLineText(lines[i]);
820                 int whiteSpace = StandardUtilities
821                     .getLeadingWhiteSpace(line);
822
823                 // silly usability hack
824
//if(lines.length != 1 && whiteSpace == 0)
825
// continue;
826

827                 int whiteSpaceWidth = StandardUtilities
828                     .getLeadingWhiteSpaceWidth(
829                     line,tabSize) + indentSize;
830                 insert(lineStart + whiteSpace,StandardUtilities
831                     .createWhiteSpace(whiteSpaceWidth,
832                     noTabs ? 0 : tabSize));
833                 remove(lineStart,whiteSpace);
834             }
835         }
836         finally
837         {
838             endCompoundEdit();
839         }
840     } //}}}
841

842     //{{{ indentLines() method
843
/**
844      * Indents all specified lines.
845      * @param start The first line to indent
846      * @param end The last line to indent
847      * @since jEdit 3.1pre3
848      */

849     public void indentLines(int start, int end)
850     {
851         try
852         {
853             beginCompoundEdit();
854             for(int i = start; i <= end; i++)
855                 indentLine(i,true);
856         }
857         finally
858         {
859             endCompoundEdit();
860         }
861     } //}}}
862

863     //{{{ indentLines() method
864
/**
865      * Indents all specified lines.
866      * @param lines The line numbers
867      * @since jEdit 3.2pre1
868      */

869     public void indentLines(int[] lines)
870     {
871         try
872         {
873             beginCompoundEdit();
874             for(int i = 0; i < lines.length; i++)
875                 indentLine(lines[i],true);
876         }
877         finally
878         {
879             endCompoundEdit();
880         }
881     } //}}}
882

883     //{{{ indentLine() method
884
/**
885      * @deprecated Use {@link #indentLine(int,boolean)} instead.
886      */

887     public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
888         boolean canDecreaseIndent)
889     {
890         return indentLine(lineIndex,canDecreaseIndent);
891     } //}}}
892

893     //{{{ indentLine() method
894
/**
895      * Indents the specified line.
896      * @param lineIndex The line number to indent
897      * @param canDecreaseIndent If true, the indent can be decreased as a
898      * result of this. Set this to false for Tab key.
899      * @return true If indentation took place, false otherwise.
900      * @since jEdit 4.2pre2
901      */

902     public boolean indentLine(int lineIndex, boolean canDecreaseIndent)
903     {
904         int[] whitespaceChars = new int[1];
905         int currentIndent = getCurrentIndentForLine(lineIndex,
906             whitespaceChars);
907         int idealIndent = getIdealIndentForLine(lineIndex);
908
909         if(idealIndent == -1 || idealIndent == currentIndent
910             || (!canDecreaseIndent && idealIndent < currentIndent))
911             return false;
912
913         // Do it
914
try
915         {
916             beginCompoundEdit();
917
918             int start = getLineStartOffset(lineIndex);
919
920             remove(start,whitespaceChars[0]);
921             insert(start,StandardUtilities.createWhiteSpace(
922                 idealIndent, getBooleanProperty("noTabs") ? 0 : getTabSize()));
923         }
924         finally
925         {
926             endCompoundEdit();
927         }
928
929         return true;
930     } //}}}
931

932     //{{{ getCurrentIndentForLine() method
933
/**
934      * Returns the line's current leading indent.
935      * @param lineIndex The line number
936      * @param whitespaceChars If this is non-null, the number of whitespace
937      * characters is stored at the 0 index
938      * @since jEdit 4.2pre2
939      */

940     public int getCurrentIndentForLine(int lineIndex, int[] whitespaceChars)
941     {
942         getLineText(lineIndex,seg);
943
944         int tabSize = getTabSize();
945
946         int currentIndent = 0;
947 loop: for(int i = 0; i < seg.count; i++)
948         {
949             char c = seg.array[seg.offset + i];
950             switch(c)
951             {
952             case ' ':
953                 currentIndent++;
954                 if(whitespaceChars != null)
955                     whitespaceChars[0]++;
956                 break;
957             case '\t':
958                 currentIndent += tabSize - (currentIndent
959                     % tabSize);
960                 if(whitespaceChars != null)
961                     whitespaceChars[0]++;
962                 break;
963             default:
964                 break loop;
965             }
966         }
967
968         return currentIndent;
969     } //}}}
970

971     //{{{ getIdealIndentForLine() method
972
/**
973      * Returns the ideal leading indent for the specified line.
974      * This will apply the various auto-indent rules.
975      * @param lineIndex The line number
976      */

977     public int getIdealIndentForLine(int lineIndex)
978     {
979         int prevLineIndex = getPriorNonEmptyLine(lineIndex);
980         int prevPrevLineIndex = prevLineIndex < 0 ? -1
981             : getPriorNonEmptyLine(prevLineIndex);
982
983         int oldIndent = prevLineIndex == -1 ? 0 :
984             StandardUtilities.getLeadingWhiteSpaceWidth(
985             getLineText(prevLineIndex),
986             getTabSize());
987         int newIndent = oldIndent;
988
989         List<IndentRule> indentRules = getIndentRules(lineIndex);
990         List<IndentAction> actions = new LinkedList<IndentAction>();
991         for (int i = 0;i<indentRules.size();i++)
992         {
993             IndentRule rule = indentRules.get(i);
994             rule.apply(this,lineIndex,prevLineIndex,
995                 prevPrevLineIndex,actions);
996         }
997
998
999         for (IndentAction action : actions)
1000        {
1001            newIndent = action.calculateIndent(this, lineIndex,
1002                    oldIndent, newIndent);
1003            if (newIndent < 0)
1004                newIndent = 0;
1005
1006            if (!action.keepChecking())
1007                break;
1008        }
1009
1010        return newIndent;
1011    } //}}}
1012

1013    //{{{ getVirtualWidth() method
1014
/**
1015     * Returns the virtual column number (taking tabs into account) of the
1016     * specified position.
1017     *
1018     * @param line The line number
1019     * @param column The column number
1020     * @since jEdit 4.1pre1
1021     */

1022    public int getVirtualWidth(int line, int column)
1023    {
1024        try
1025        {
1026            readLock();
1027
1028            int start = getLineStartOffset(line);
1029            getText(start,column,seg);
1030
1031            return StandardUtilities.getVirtualWidth(seg,getTabSize());
1032        }
1033        finally
1034        {
1035            readUnlock();
1036        }
1037    } //}}}
1038

1039    //{{{ getOffsetOfVirtualColumn() method
1040
/**
1041     * Returns the offset of a virtual column number (taking tabs
1042     * into account) relative to the start of the line in question.
1043     *
1044     * @param line The line number
1045     * @param column The virtual column number
1046     * @param totalVirtualWidth If this array is non-null, the total
1047     * virtual width will be stored in its first location if this method
1048     * returns -1.
1049     *
1050     * @return -1 if the column is out of bounds
1051     *
1052     * @since jEdit 4.1pre1
1053     */

1054    public int getOffsetOfVirtualColumn(int line, int column,
1055        int[] totalVirtualWidth)
1056    {
1057        try
1058        {
1059            readLock();
1060
1061            getLineText(line,seg);
1062
1063            return StandardUtilities.getOffsetOfVirtualColumn(seg,
1064                getTabSize(),column,totalVirtualWidth);
1065        }
1066        finally
1067        {
1068            readUnlock();
1069        }
1070    } //}}}
1071

1072    //{{{ insertAtColumn() method
1073
/**
1074     * Like the {@link #insert(int,String)} method, but inserts the string at
1075     * the specified virtual column. Inserts spaces as appropriate if
1076     * the line is shorter than the column.
1077     * @param line The line number
1078     * @param col The virtual column number
1079     * @param str The string
1080     */

1081    public void insertAtColumn(int line, int col, String JavaDoc str)
1082    {
1083        try
1084        {
1085            writeLock();
1086
1087            int[] total = new int[1];
1088            int offset = getOffsetOfVirtualColumn(line,col,total);
1089            if(offset == -1)
1090            {
1091                offset = getLineEndOffset(line) - 1;
1092                str = StandardUtilities.createWhiteSpace(col - total[0],0) + str;
1093            }
1094            else
1095                offset += getLineStartOffset(line);
1096
1097            insert(offset,str);
1098        }
1099        finally
1100        {
1101            writeUnlock();
1102        }
1103    } //}}}
1104

1105    //{{{ insertIndented() method
1106
/**
1107     * Inserts a string into the buffer, indenting each line of the string
1108     * to match the indent of the first line.
1109     *
1110     * @param offset The offset
1111     * @param text The text
1112     *
1113     * @return The number of characters of indent inserted on each new
1114     * line. This is used by the abbreviations code.
1115     *
1116     * @since jEdit 4.2pre14
1117     */

1118    public int insertIndented(int offset, String JavaDoc text)
1119    {
1120        try
1121        {
1122            beginCompoundEdit();
1123
1124            // obtain the leading indent for later use
1125
int firstLine = getLineOfOffset(offset);
1126            String JavaDoc lineText = getLineText(firstLine);
1127            int leadingIndent
1128                = StandardUtilities.getLeadingWhiteSpaceWidth(
1129                lineText,getTabSize());
1130
1131            String JavaDoc whiteSpace = StandardUtilities.createWhiteSpace(
1132                leadingIndent,getBooleanProperty("noTabs")
1133                ? 0 : getTabSize());
1134
1135            insert(offset,text);
1136
1137            int lastLine = getLineOfOffset(offset + text.length());
1138
1139            // note that if firstLine == lastLine, loop does not
1140
// execute
1141
for(int i = firstLine + 1; i <= lastLine; i++)
1142            {
1143                insert(getLineStartOffset(i),whiteSpace);
1144            }
1145
1146            return whiteSpace.length();
1147        }
1148        finally
1149        {
1150            endCompoundEdit();
1151        }
1152    } //}}}
1153

1154    //{{{ isElectricKey() method
1155
/**
1156     * Should inserting this character trigger a re-indent of
1157     * the current line?
1158     * @since jEdit 4.3pre2
1159     * @deprecated Use #isElectricKey(char,int)
1160     */

1161    public boolean isElectricKey(char ch)
1162    {
1163        Mode mode = jEdit.getMode(tokenMarker.getMainRuleSet().getModeName());
1164        return mode.isElectricKey(ch);
1165    } //}}}
1166

1167    //{{{ isElectricKey() method
1168
/**
1169     * Should inserting this character trigger a re-indent of
1170     * the current line?
1171     * @since jEdit 4.3pre9
1172     */

1173    public boolean isElectricKey(char ch, int line)
1174    {
1175        TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
1176        Mode mode = jEdit.getMode(ctx.rules.getModeName());
1177
1178        // mode can be null, though that's probably an error "further up":
1179
if( mode == null )
1180            return false;
1181        return mode.isElectricKey(ch);
1182    } //}}}
1183

1184    //}}}
1185

1186    //{{{ Syntax highlighting
1187

1188    //{{{ markTokens() method
1189
/**
1190     * Returns the syntax tokens for the specified line.
1191     * @param lineIndex The line number
1192     * @param tokenHandler The token handler that will receive the syntax
1193     * tokens
1194     * @since jEdit 4.1pre1
1195     */

1196    public void markTokens(int lineIndex, TokenHandler tokenHandler)
1197    {
1198        Segment seg;
1199        if(SwingUtilities.isEventDispatchThread())
1200            seg = this.seg;
1201        else
1202            seg = new Segment();
1203
1204        if(lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
1205            throw new ArrayIndexOutOfBoundsException JavaDoc(lineIndex);
1206
1207        int firstInvalidLineContext = lineMgr.getFirstInvalidLineContext();
1208        int start;
1209        if(textMode || firstInvalidLineContext == -1)
1210        {
1211            start = lineIndex;
1212        }
1213        else
1214        {
1215            start = Math.min(firstInvalidLineContext,
1216                lineIndex);
1217        }
1218
1219        if(Debug.TOKEN_MARKER_DEBUG)
1220            Log.log(Log.DEBUG,this,"tokenize from " + start + " to " + lineIndex);
1221        TokenMarker.LineContext oldContext = null;
1222        TokenMarker.LineContext context = null;
1223        for(int i = start; i <= lineIndex; i++)
1224        {
1225            getLineText(i,seg);
1226
1227            oldContext = lineMgr.getLineContext(i);
1228
1229            TokenMarker.LineContext prevContext = (
1230                (i == 0 || textMode) ? null
1231                : lineMgr.getLineContext(i - 1)
1232            );
1233
1234            context = tokenMarker.markTokens(prevContext,
1235                (i == lineIndex ? tokenHandler
1236                : DummyTokenHandler.INSTANCE),seg);
1237            lineMgr.setLineContext(i,context);
1238        }
1239
1240        int lineCount = lineMgr.getLineCount();
1241        if(lineCount - 1 == lineIndex)
1242            lineMgr.setFirstInvalidLineContext(-1);
1243        else if(oldContext != context)
1244            lineMgr.setFirstInvalidLineContext(lineIndex + 1);
1245        else if(firstInvalidLineContext == -1)
1246            /* do nothing */;
1247        else
1248        {
1249            lineMgr.setFirstInvalidLineContext(Math.max(
1250                firstInvalidLineContext,lineIndex + 1));
1251        }
1252    } //}}}
1253

1254    //{{{ getTokenMarker() method
1255
public TokenMarker getTokenMarker()
1256    {
1257        return tokenMarker;
1258    } //}}}
1259

1260    //{{{ setTokenMarker() method
1261
public void setTokenMarker(TokenMarker tokenMarker)
1262    {
1263        TokenMarker oldTokenMarker = this.tokenMarker;
1264
1265        this.tokenMarker = tokenMarker;
1266
1267        // don't do this on initial token marker
1268
if(oldTokenMarker != null && tokenMarker != oldTokenMarker)
1269        {
1270            lineMgr.setFirstInvalidLineContext(0);
1271        }
1272    } //}}}
1273

1274    //{{{ createPosition() method
1275
/**
1276     * Creates a floating position.
1277     * @param offset The offset
1278     */

1279    public Position createPosition(int offset)
1280    {
1281        try
1282        {
1283            readLock();
1284
1285            if(offset < 0 || offset > contentMgr.getLength())
1286                throw new ArrayIndexOutOfBoundsException JavaDoc(offset);
1287
1288            return positionMgr.createPosition(offset);
1289        }
1290        finally
1291        {
1292            readUnlock();
1293        }
1294    } //}}}
1295

1296    //}}}
1297

1298    //{{{ Property methods
1299

1300    //{{{ getTabSize() method
1301
/**
1302     * Returns the tab size used in this buffer. This is equivalent
1303     * to calling <code>getProperty("tabSize")</code>.
1304     * This method is thread-safe.
1305     */

1306    public int getTabSize()
1307    {
1308        int tabSize = getIntegerProperty("tabSize",8);
1309        if(tabSize <= 0)
1310            return 8;
1311        else
1312            return tabSize;
1313    } //}}}
1314

1315    //{{{ getIndentSize() method
1316
/**
1317     * Returns the indent size used in this buffer. This is equivalent
1318     * to calling <code>getProperty("indentSize")</code>.
1319     * This method is thread-safe.
1320     * @since jEdit 2.7pre1
1321     */

1322    public int getIndentSize()
1323    {
1324        int indentSize = getIntegerProperty("indentSize",8);
1325        if(indentSize <= 0)
1326            return 8;
1327        else
1328            return indentSize;
1329    } //}}}
1330

1331    //{{{ getProperty() method
1332
/**
1333     * Returns the value of a buffer-local property.<p>
1334     *
1335     * Using this method is generally discouraged, because it returns an
1336     * <code>Object</code> which must be cast to another type
1337     * in order to be useful, and this can cause problems if the object
1338     * is of a different type than what the caller expects.<p>
1339     *
1340     * The following methods should be used instead:
1341     * <ul>
1342     * <li>{@link #getStringProperty(String)}</li>
1343     * <li>{@link #getBooleanProperty(String)}</li>
1344     * <li>{@link #getIntegerProperty(String,int)}</li>
1345     * </ul>
1346     *
1347     * This method is thread-safe.
1348     *
1349     * @param name The property name. For backwards compatibility, this
1350     * is an <code>Object</code>, not a <code>String</code>.
1351     */

1352    public Object JavaDoc getProperty(Object JavaDoc name)
1353    {
1354        synchronized(propertyLock)
1355        {
1356            // First try the buffer-local properties
1357
PropValue o = properties.get(name);
1358            if(o != null)
1359                return o.value;
1360
1361            // For backwards compatibility
1362
if(!(name instanceof String JavaDoc))
1363                return null;
1364
1365            Object JavaDoc retVal = getDefaultProperty((String JavaDoc)name);
1366
1367            if(retVal == null)
1368                return null;
1369            else
1370            {
1371                properties.put(name,new PropValue(retVal,true));
1372                return retVal;
1373            }
1374        }
1375    } //}}}
1376

1377    //{{{ getDefaultProperty() method
1378
public Object JavaDoc getDefaultProperty(String JavaDoc key)
1379    {
1380        return null;
1381    } //}}}
1382

1383    //{{{ setProperty() method
1384
/**
1385     * Sets the value of a buffer-local property.
1386     * @param name The property name
1387     * @param value The property value
1388     * @since jEdit 4.0pre1
1389     */

1390    public void setProperty(String JavaDoc name, Object JavaDoc value)
1391    {
1392        if(value == null)
1393            properties.remove(name);
1394        else
1395        {
1396            PropValue test = properties.get(name);
1397            if(test == null)
1398                properties.put(name,new PropValue(value,false));
1399            else if(test.value.equals(value))
1400            {
1401                // do nothing
1402
}
1403            else
1404            {
1405                test.value = value;
1406                test.defaultValue = false;
1407            }
1408        }
1409    } //}}}
1410

1411    //{{{ setDefaultProperty() method
1412
public void setDefaultProperty(String JavaDoc name, Object JavaDoc value)
1413    {
1414        properties.put(name,new PropValue(value,true));
1415    } //}}}
1416

1417    //{{{ unsetProperty() method
1418
/**
1419     * Clears the value of a buffer-local property.
1420     * @param name The property name
1421     * @since jEdit 4.0pre1
1422     */

1423    public void unsetProperty(String JavaDoc name)
1424    {
1425        properties.remove(name);
1426    } //}}}
1427

1428    //{{{ resetCachedProperties() method
1429
public void resetCachedProperties()
1430    {
1431        // Need to reset properties that were cached defaults,
1432
// since the defaults might have changed.
1433
Iterator<PropValue> iter = properties.values().iterator();
1434        while(iter.hasNext())
1435        {
1436            PropValue value = iter.next();
1437            if(value.defaultValue)
1438                iter.remove();
1439        }
1440    } //}}}
1441

1442    //{{{ getStringProperty() method
1443
/**
1444     * Returns the value of a string property. This method is thread-safe.
1445     * @param name The property name
1446     * @since jEdit 4.0pre1
1447     */

1448    public String JavaDoc getStringProperty(String JavaDoc name)
1449    {
1450        Object JavaDoc obj = getProperty(name);
1451        if(obj != null)
1452            return obj.toString();
1453        else
1454            return null;
1455    } //}}}
1456

1457    //{{{ setStringProperty() method
1458
/**
1459     * Sets a string property.
1460     * @param name The property name
1461     * @param value The value
1462     * @since jEdit 4.0pre1
1463     */

1464    public void setStringProperty(String JavaDoc name, String JavaDoc value)
1465    {
1466        setProperty(name,value);
1467    } //}}}
1468

1469    //{{{ getBooleanProperty() method
1470
/**
1471     * Returns the value of a boolean property. This method is thread-safe.
1472     * @param name The property name
1473     * @since jEdit 4.0pre1
1474     */

1475    public boolean getBooleanProperty(String JavaDoc name)
1476    {
1477        Object JavaDoc obj = getProperty(name);
1478        if(obj instanceof Boolean JavaDoc)
1479            return (Boolean JavaDoc)obj;
1480        else if("true".equals(obj) || "on".equals(obj) || "yes".equals(obj))
1481            return true;
1482        else
1483            return false;
1484    } //}}}
1485

1486    //{{{ setBooleanProperty() method
1487
/**
1488     * Sets a boolean property.
1489     * @param name The property name
1490     * @param value The value
1491     * @since jEdit 4.0pre1
1492     */

1493    public void setBooleanProperty(String JavaDoc name, boolean value)
1494    {
1495        setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
1496    } //}}}
1497

1498    //{{{ getIntegerProperty() method
1499
/**
1500     * Returns the value of an integer property. This method is thread-safe.
1501     * @param name The property name
1502     * @since jEdit 4.0pre1
1503     */

1504    public int getIntegerProperty(String JavaDoc name, int defaultValue)
1505    {
1506        boolean defaultValueFlag;
1507        Object JavaDoc obj;
1508        PropValue value = properties.get(name);
1509        if(value != null)
1510        {
1511            obj = value.value;
1512            defaultValueFlag = value.defaultValue;
1513        }
1514        else
1515        {
1516            obj = getProperty(name);
1517            // will be cached from now on...
1518
defaultValueFlag = true;
1519        }
1520
1521        if(obj == null)
1522            return defaultValue;
1523        else if(obj instanceof Number JavaDoc)
1524            return ((Number JavaDoc)obj).intValue();
1525        else
1526        {
1527            try
1528            {
1529                int returnValue = Integer.parseInt(
1530                    obj.toString().trim());
1531                properties.put(name,new PropValue(
1532                    returnValue,
1533                    defaultValueFlag));
1534                return returnValue;
1535            }
1536            catch(Exception JavaDoc e)
1537            {
1538                return defaultValue;
1539            }
1540        }
1541    } //}}}
1542

1543    //{{{ setIntegerProperty() method
1544
/**
1545     * Sets an integer property.
1546     * @param name The property name
1547     * @param value The value
1548     * @since jEdit 4.0pre1
1549     */

1550    public void setIntegerProperty(String JavaDoc name, int value)
1551    {
1552        setProperty(name,value);
1553    } //}}}
1554

1555    //{{{ getPatternProperty()
1556
/**
1557     * Returns the value of a property as a regular expression.
1558     * This method is thread-safe.
1559     * @param name The property name
1560     * @param flags Regular expression compilation flags
1561     * @since jEdit 4.3pre5
1562     */

1563    public Pattern JavaDoc getPatternProperty(String JavaDoc name, int flags) {
1564        synchronized(propertyLock)
1565        {
1566            boolean defaultValueFlag;
1567            Object JavaDoc obj;
1568            PropValue value = properties.get(name);
1569            if(value != null)
1570            {
1571                obj = value.value;
1572                defaultValueFlag = value.defaultValue;
1573            }
1574            else
1575            {
1576                obj = getProperty(name);
1577                // will be cached from now on...
1578
defaultValueFlag = true;
1579            }
1580
1581            if(obj == null)
1582                return null;
1583            else if (obj instanceof Pattern JavaDoc)
1584                return (Pattern JavaDoc) obj;
1585            else
1586            {
1587                Pattern JavaDoc re = Pattern.compile(obj.toString(),flags);
1588                properties.put(name,new PropValue(re,
1589                    defaultValueFlag));
1590                return re;
1591            }
1592        }
1593    } //}}}
1594

1595    //{{{ getRuleSetAtOffset() method
1596
/**
1597     * Returns the syntax highlighting ruleset at the specified offset.
1598     * @since jEdit 4.1pre1
1599     */

1600    public ParserRuleSet getRuleSetAtOffset(int offset)
1601    {
1602        int line = getLineOfOffset(offset);
1603        offset -= getLineStartOffset(line);
1604        if(offset != 0)
1605            offset--;
1606
1607        DefaultTokenHandler tokens = new DefaultTokenHandler();
1608        markTokens(line,tokens);
1609        Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
1610        return token.rules;
1611    } //}}}
1612

1613    //{{{ getKeywordMapAtOffset() method
1614
/**
1615     * Returns the syntax highlighting keyword map in effect at the
1616     * specified offset. Used by the <b>Complete Word</b> command to
1617     * complete keywords.
1618     * @param offset The offset
1619     * @since jEdit 4.0pre3
1620     */

1621    public KeywordMap getKeywordMapAtOffset(int offset)
1622    {
1623        return getRuleSetAtOffset(offset).getKeywords();
1624    } //}}}
1625

1626    //{{{ getContextSensitiveProperty() method
1627
/**
1628     * Some settings, like comment start and end strings, can
1629     * vary between different parts of a buffer (HTML text and inline
1630     * JavaScript, for example).
1631     * @param offset The offset
1632     * @param name The property name
1633     * @since jEdit 4.0pre3
1634     */

1635    public String JavaDoc getContextSensitiveProperty(int offset, String JavaDoc name)
1636    {
1637        ParserRuleSet rules = getRuleSetAtOffset(offset);
1638
1639        Object JavaDoc value = null;
1640
1641        Map<String JavaDoc, String JavaDoc> rulesetProps = rules.getProperties();
1642        if(rulesetProps != null)
1643            value = rulesetProps.get(name);
1644
1645        if(value == null)
1646            return null;
1647        else
1648            return String.valueOf(value);
1649    } //}}}
1650

1651    //}}}
1652

1653    //{{{ Folding methods
1654

1655    //{{{ isFoldStart() method
1656
/**
1657     * Returns if the specified line begins a fold.
1658     * @since jEdit 3.1pre1
1659     */

1660    public boolean isFoldStart(int line)
1661    {
1662        return line != getLineCount() - 1
1663            && getFoldLevel(line) < getFoldLevel(line + 1);
1664    } //}}}
1665

1666    //{{{ isFoldEnd() method
1667
/**
1668     * Returns if the specified line ends a fold.
1669     * @since jEdit 4.2pre5
1670     */

1671    public boolean isFoldEnd(int line)
1672    {
1673        return line != getLineCount() - 1
1674            && getFoldLevel(line) > getFoldLevel(line + 1);
1675    } //}}}
1676

1677    //{{{ invalidateCachedFoldLevels() method
1678
/**
1679     * Invalidates all cached fold level information.
1680     * @since jEdit 4.1pre11
1681     */

1682    public void invalidateCachedFoldLevels()
1683    {
1684        lineMgr.setFirstInvalidFoldLevel(0);
1685        fireFoldLevelChanged(0,getLineCount());
1686    } //}}}
1687

1688    //{{{ getFoldLevel() method
1689
/**
1690     * Returns the fold level of the specified line.
1691     * @param line A physical line index
1692     * @since jEdit 3.1pre1
1693     */

1694    public int getFoldLevel(int line)
1695    {
1696        if(line < 0 || line >= lineMgr.getLineCount())
1697            throw new ArrayIndexOutOfBoundsException JavaDoc(line);
1698
1699        if(foldHandler instanceof DummyFoldHandler)
1700            return 0;
1701
1702        int firstInvalidFoldLevel = lineMgr.getFirstInvalidFoldLevel();
1703        if(firstInvalidFoldLevel == -1 || line < firstInvalidFoldLevel)
1704        {
1705            return lineMgr.getFoldLevel(line);
1706        }
1707        else
1708        {
1709            if(Debug.FOLD_DEBUG)
1710                Log.log(Log.DEBUG,this,"Invalid fold levels from " + firstInvalidFoldLevel + " to " + line);
1711
1712            int newFoldLevel = 0;
1713            boolean changed = false;
1714
1715            for(int i = firstInvalidFoldLevel; i <= line; i++)
1716            {
1717                newFoldLevel = foldHandler.getFoldLevel(this,i,seg);
1718                if(newFoldLevel != lineMgr.getFoldLevel(i))
1719                {
1720                    if(Debug.FOLD_DEBUG)
1721                        Log.log(Log.DEBUG,this,i + " fold level changed");
1722                    changed = true;
1723                }
1724                lineMgr.setFoldLevel(i,newFoldLevel);
1725            }
1726
1727            if(line == lineMgr.getLineCount() - 1)
1728                lineMgr.setFirstInvalidFoldLevel(-1);
1729            else
1730                lineMgr.setFirstInvalidFoldLevel(line + 1);
1731
1732            if(changed)
1733            {
1734                if(Debug.FOLD_DEBUG)
1735                    Log.log(Log.DEBUG,this,"fold level changed: " + firstInvalidFoldLevel + ',' + line);
1736                fireFoldLevelChanged(firstInvalidFoldLevel,line);
1737            }
1738
1739            return newFoldLevel;
1740        }
1741    } //}}}
1742

1743    //{{{ getFoldAtLine() method
1744
/**
1745     * Returns an array. The first element is the start line, the
1746     * second element is the end line, of the fold containing the
1747     * specified line number.
1748     * @param line The line number
1749     * @since jEdit 4.0pre3
1750     */

1751    public int[] getFoldAtLine(int line)
1752    {
1753        int start, end;
1754
1755        if(isFoldStart(line))
1756        {
1757            start = line;
1758            int foldLevel = getFoldLevel(line);
1759
1760            line++;
1761
1762            while(getFoldLevel(line) > foldLevel)
1763            {
1764                line++;
1765
1766                if(line == getLineCount())
1767                    break;
1768            }
1769
1770            end = line - 1;
1771        }
1772        else
1773        {
1774            start = line;
1775            int foldLevel = getFoldLevel(line);
1776            while(getFoldLevel(start) >= foldLevel)
1777            {
1778                if(start == 0)
1779                    break;
1780                else
1781                    start--;
1782            }
1783
1784            end = line;
1785            while(getFoldLevel(end) >= foldLevel)
1786            {
1787                end++;
1788
1789                if(end == getLineCount())
1790                    break;
1791            }
1792
1793            end--;
1794        }
1795
1796        while(getLineLength(end) == 0 && end > start)
1797            end--;
1798
1799        return new int[] { start, end };
1800    } //}}}
1801

1802    //{{{ getFoldHandler() method
1803
/**
1804     * Returns the current buffer's fold handler.
1805     * @since jEdit 4.2pre1
1806     */

1807    public FoldHandler getFoldHandler()
1808    {
1809        return foldHandler;
1810    } //}}}
1811

1812    //{{{ setFoldHandler() method
1813
/**
1814     * Sets the buffer's fold handler.
1815     * @since jEdit 4.2pre2
1816     */

1817    public void setFoldHandler(FoldHandler foldHandler)
1818    {
1819        FoldHandler oldFoldHandler = this.foldHandler;
1820
1821        if(foldHandler.equals(oldFoldHandler))
1822            return;
1823
1824        this.foldHandler = foldHandler;
1825
1826        lineMgr.setFirstInvalidFoldLevel(0);
1827
1828        fireFoldHandlerChanged();
1829    } //}}}
1830

1831    //}}}
1832

1833    //{{{ Undo
1834

1835    //{{{ undo() method
1836
/**
1837     * Undoes the most recent edit.
1838     *
1839     * @since jEdit 4.0pre1
1840     */

1841    public void undo(JEditTextArea textArea)
1842    {
1843        if(undoMgr == null)
1844            return;
1845
1846        if(!isEditable())
1847        {
1848            textArea.getToolkit().beep();
1849            return;
1850        }
1851
1852        try
1853        {
1854            writeLock();
1855
1856            undoInProgress = true;
1857            int caret = undoMgr.undo();
1858
1859            if(caret == -1)
1860                textArea.getToolkit().beep();
1861            else
1862                textArea.setCaretPosition(caret);
1863
1864            fireTransactionComplete();
1865        }
1866        finally
1867        {
1868            undoInProgress = false;
1869
1870            writeUnlock();
1871        }
1872    } //}}}
1873

1874    //{{{ redo() method
1875
/**
1876     * Redoes the most recently undone edit.
1877     *
1878     * @since jEdit 2.7pre2
1879     */

1880    public void redo(JEditTextArea textArea)
1881    {
1882        if(undoMgr == null)
1883            return;
1884
1885        if(!isEditable())
1886        {
1887            Toolkit.getDefaultToolkit().beep();
1888            return;
1889        }
1890
1891        try
1892        {
1893            writeLock();
1894
1895            undoInProgress = true;
1896            int caret = undoMgr.redo();
1897            if(caret == -1)
1898                textArea.getToolkit().beep();
1899            else
1900                textArea.setCaretPosition(caret);
1901
1902            fireTransactionComplete();
1903        }
1904        finally
1905        {
1906            undoInProgress = false;
1907
1908            writeUnlock();
1909        }
1910    } //}}}
1911

1912    //{{{ isTransactionInProgress() method
1913
/**
1914     * Returns if an undo or compound edit is currently in progress. If this
1915     * method returns true, then eventually a
1916     * {@link org.gjt.sp.jedit.buffer.BufferListener#transactionComplete(JEditBuffer)}
1917     * buffer event will get fired.
1918     * @since jEdit 4.0pre6
1919     */

1920    public boolean isTransactionInProgress()
1921    {
1922        return transaction || undoInProgress || insideCompoundEdit();
1923    } //}}}
1924

1925    //{{{ beginCompoundEdit() method
1926
/**
1927     * Starts a compound edit. All edits from now on until
1928     * {@link #endCompoundEdit()} are called will be merged
1929     * into one. This can be used to make a complex operation
1930     * undoable in one step. Nested calls to
1931     * {@link #beginCompoundEdit()} behave as expected,
1932     * requiring the same number of {@link #endCompoundEdit()}
1933     * calls to end the edit.
1934     * @see #endCompoundEdit()
1935     */

1936    public void beginCompoundEdit()
1937    {
1938        try
1939        {
1940            writeLock();
1941
1942            undoMgr.beginCompoundEdit();
1943        }
1944        finally
1945        {
1946            writeUnlock();
1947        }
1948    } //}}}
1949

1950    //{{{ endCompoundEdit() method
1951
/**
1952     * Ends a compound edit. All edits performed since
1953     * {@link #beginCompoundEdit()} was called can now
1954     * be undone in one step by calling {@link #undo(JEditTextArea)}.
1955     * @see #beginCompoundEdit()
1956     */

1957    public void endCompoundEdit()
1958    {
1959        try
1960        {
1961            writeLock();
1962
1963            undoMgr.endCompoundEdit();
1964
1965            if(!insideCompoundEdit())
1966                fireTransactionComplete();
1967        }
1968        finally
1969        {
1970            writeUnlock();
1971        }
1972    }//}}}
1973

1974    //{{{ insideCompoundEdit() method
1975
/**
1976     * Returns if a compound edit is currently active.
1977     * @since jEdit 3.1pre1
1978     */

1979    public boolean insideCompoundEdit()
1980    {
1981        return undoMgr.insideCompoundEdit();
1982    } //}}}
1983

1984    //{{{ isUndoInProgress() method
1985
/**
1986     * Returns if an undo or redo is currently being performed.
1987     * @since jEdit 4.3pre3
1988     */

1989    public boolean isUndoInProgress()
1990    {
1991        return undoInProgress;
1992    } //}}}
1993

1994    //}}}
1995

1996    //{{{ Buffer events
1997
public static final int NORMAL_PRIORITY = 0;
1998    public static final int HIGH_PRIORITY = 1;
1999
2000    static class Listener
2001    {
2002        BufferListener listener;
2003        int priority;
2004
2005        Listener(BufferListener listener, int priority)
2006        {
2007            this.listener = listener;
2008            this.priority = priority;
2009        }
2010    }
2011
2012    //{{{ addBufferListener() method
2013
/**
2014     * Adds a buffer change listener.
2015     * @param listener The listener
2016     * @param priority Listeners with HIGH_PRIORITY get the event before
2017     * listeners with NORMAL_PRIORITY
2018     * @since jEdit 4.3pre3
2019     */

2020    public void addBufferListener(BufferListener listener,
2021        int priority)
2022    {
2023        Listener l = new Listener(listener,priority);
2024        for(int i = 0; i < bufferListeners.size(); i++)
2025        {
2026            Listener _l = bufferListeners.get(i);
2027            if(_l.priority < priority)
2028            {
2029                bufferListeners.add(i,l);
2030                return;
2031            }
2032        }
2033        bufferListeners.add(l);
2034    } //}}}
2035

2036    //{{{ addBufferListener() method
2037
/**
2038     * Adds a buffer change listener.
2039     * @param listener The listener
2040     * @since jEdit 4.3pre3
2041     */

2042    public void addBufferListener(BufferListener listener)
2043    {
2044        addBufferListener(listener,NORMAL_PRIORITY);
2045    } //}}}
2046

2047    //{{{ removeBufferListener() method
2048
/**
2049     * Removes a buffer change listener.
2050     * @param listener The listener
2051     * @since jEdit 4.3pre3
2052     */

2053    public void removeBufferListener(BufferListener listener)
2054    {
2055        for(int i = 0; i < bufferListeners.size(); i++)
2056        {
2057            if(bufferListeners.get(i).listener == listener)
2058            {
2059                bufferListeners.remove(i);
2060                return;
2061            }
2062        }
2063    } //}}}
2064

2065    //{{{ getBufferListeners() method
2066
/**
2067     * Returns an array of registered buffer change listeners.
2068     * @since jEdit 4.3pre3
2069     */

2070    public BufferListener[] getBufferListeners()
2071    {
2072        BufferListener[] returnValue
2073            = new BufferListener[
2074            bufferListeners.size()];
2075        for(int i = 0; i < returnValue.length; i++)
2076        {
2077            returnValue[i] = bufferListeners.get(i).listener;
2078        }
2079        return returnValue;
2080    } //}}}
2081

2082    //}}}
2083

2084    //{{{ Protected members
2085

2086    protected Segment seg;
2087    protected boolean textMode;
2088    protected UndoManager undoMgr;
2089
2090    //{{{ Event firing methods
2091

2092    //{{{ fireFoldLevelChanged() method
2093
protected void fireFoldLevelChanged(int start, int end)
2094    {
2095        for(int i = 0; i < bufferListeners.size(); i++)
2096        {
2097            try
2098            {
2099                getListener(i).foldLevelChanged(this,start,end);
2100            }
2101            catch(Throwable JavaDoc t)
2102            {
2103                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2104                Log.log(Log.ERROR,this,t);
2105            }
2106        }
2107    } //}}}
2108

2109    //{{{ fireContentInserted() method
2110
protected void fireContentInserted(int startLine, int offset,
2111        int numLines, int length)
2112    {
2113        for(int i = 0; i < bufferListeners.size(); i++)
2114        {
2115            try
2116            {
2117                getListener(i).contentInserted(this,startLine,
2118                    offset,numLines,length);
2119            }
2120            catch(Throwable JavaDoc t)
2121            {
2122                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2123                Log.log(Log.ERROR,this,t);
2124            }
2125        }
2126    } //}}}
2127

2128    //{{{ fireContentRemoved() method
2129
protected void fireContentRemoved(int startLine, int offset,
2130        int numLines, int length)
2131    {
2132        for(int i = 0; i < bufferListeners.size(); i++)
2133        {
2134            try
2135            {
2136                getListener(i).contentRemoved(this,startLine,
2137                    offset,numLines,length);
2138            }
2139            catch(Throwable JavaDoc t)
2140            {
2141                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2142                Log.log(Log.ERROR,this,t);
2143            }
2144        }
2145    } //}}}
2146

2147    //{{{ firePreContentRemoved() method
2148
protected void firePreContentRemoved(int startLine, int offset,
2149        int numLines, int length)
2150    {
2151        for(int i = 0; i < bufferListeners.size(); i++)
2152        {
2153            try
2154            {
2155                getListener(i).preContentRemoved(this,startLine,
2156                    offset,numLines,length);
2157            }
2158            catch(Throwable JavaDoc t)
2159            {
2160                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2161                Log.log(Log.ERROR,this,t);
2162            }
2163        }
2164    } //}}}
2165

2166    //{{{ fireTransactionComplete() method
2167
protected void fireTransactionComplete()
2168    {
2169        for(int i = 0; i < bufferListeners.size(); i++)
2170        {
2171            try
2172            {
2173                getListener(i).transactionComplete(this);
2174            }
2175            catch(Throwable JavaDoc t)
2176            {
2177                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2178                Log.log(Log.ERROR,this,t);
2179            }
2180        }
2181    } //}}}
2182

2183    //{{{ fireFoldHandlerChanged() method
2184
protected void fireFoldHandlerChanged()
2185    {
2186        for(int i = 0; i < bufferListeners.size(); i++)
2187        {
2188            try
2189            {
2190                getListener(i).foldHandlerChanged(this);
2191            }
2192            catch(Throwable JavaDoc t)
2193            {
2194                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2195                Log.log(Log.ERROR,this,t);
2196            }
2197        }
2198    } //}}}
2199

2200    //{{{ fireBufferLoaded() method
2201
protected void fireBufferLoaded()
2202    {
2203        for(int i = 0; i < bufferListeners.size(); i++)
2204        {
2205            try
2206            {
2207                getListener(i).bufferLoaded(this);
2208            }
2209            catch(Throwable JavaDoc t)
2210            {
2211                Log.log(Log.ERROR,this,"Exception while sending buffer event to "+getListener(i)+" :");
2212                Log.log(Log.ERROR,this,t);
2213            }
2214        }
2215    } //}}}
2216

2217    //}}}
2218

2219    //{{{ isFileReadOnly() method
2220
protected boolean isFileReadOnly()
2221    {
2222        return readOnly;
2223    } //}}}
2224

2225    //{{{ setFileReadOnly() method
2226
protected void setFileReadOnly(boolean readOnly)
2227    {
2228        this.readOnly = readOnly;
2229    } //}}}
2230

2231    //{{{ loadText() method
2232
protected void loadText(Segment seg, IntegerArray endOffsets)
2233    {
2234        if(seg == null)
2235            seg = new Segment(new char[1024],0,0);
2236
2237        if(endOffsets == null)
2238        {
2239            endOffsets = new IntegerArray();
2240            endOffsets.add(1);
2241        }
2242
2243        try
2244        {
2245            writeLock();
2246
2247            // For `reload' command
2248
// contentMgr.remove() changes this!
2249
int length = getLength();
2250
2251            firePreContentRemoved(0,0,getLineCount()
2252                - 1,length);
2253
2254            contentMgr.remove(0,length);
2255            lineMgr.contentRemoved(0,0,getLineCount()
2256                - 1,length);
2257            positionMgr.contentRemoved(0,length);
2258            fireContentRemoved(0,0,getLineCount()
2259                - 1,length);
2260
2261            // theoretically a segment could
2262
// have seg.offset != 0 but
2263
// SegmentBuffer never does that
2264
contentMgr._setContent(seg.array,seg.count);
2265
2266            lineMgr._contentInserted(endOffsets);
2267            positionMgr.contentInserted(0,seg.count);
2268
2269            fireContentInserted(0,0,
2270                endOffsets.getSize() - 1,
2271                seg.count - 1);
2272        }
2273        finally
2274        {
2275            writeUnlock();
2276        }
2277    } //}}}
2278

2279    //{{{ invalidateFoldLevels() method
2280
protected void invalidateFoldLevels()
2281    {
2282        lineMgr.setFirstInvalidFoldLevel(0);
2283    } //}}}
2284

2285    //{{{ parseBufferLocalProperties() method
2286
protected void parseBufferLocalProperties()
2287    {
2288        int lastLine = Math.min(9,getLineCount() - 1);
2289        parseBufferLocalProperties(getText(0,getLineEndOffset(lastLine) - 1));
2290
2291        // first line for last 10 lines, make sure not to overlap
2292
// with the first 10
2293
int firstLine = Math.max(lastLine + 1, getLineCount() - 10);
2294        if(firstLine < getLineCount())
2295        {
2296            int length = getLineEndOffset(getLineCount() - 1)
2297                - (getLineStartOffset(firstLine) + 1);
2298            parseBufferLocalProperties(getText(getLineStartOffset(firstLine),length));
2299        }
2300    } //}}}
2301

2302    //{{{ Used to store property values
2303
protected static class PropValue
2304    {
2305        PropValue(Object JavaDoc value, boolean defaultValue)
2306        {
2307            if(value == null)
2308                throw new NullPointerException JavaDoc();
2309            this.value = value;
2310            this.defaultValue = defaultValue;
2311        }
2312
2313        Object JavaDoc value;
2314
2315        /**
2316         * If this is true, then this value is cached from the mode
2317         * or global defaults, so when the defaults change this property
2318         * value must be reset.
2319         */

2320        boolean defaultValue;
2321
2322        /**
2323         * For debugging purposes.
2324         */

2325        public String JavaDoc toString()
2326        {
2327            return value.toString();
2328        }
2329    } //}}}
2330

2331    //}}}
2332

2333    //{{{ Private members
2334
private List<Listener> bufferListeners;
2335    private ReadWriteLock lock;
2336    private ContentManager contentMgr;
2337    private LineManager lineMgr;
2338    private PositionManager positionMgr;
2339    private FoldHandler foldHandler;
2340    private IntegerArray integerArray;
2341    private TokenMarker tokenMarker;
2342    private boolean undoInProgress;
2343    private boolean dirty;
2344    private boolean readOnly;
2345    private boolean readOnlyOverride;
2346    private boolean transaction;
2347    private boolean loading;
2348    private boolean io;
2349    private final Map<Object JavaDoc, PropValue> properties;
2350    private final Object JavaDoc propertyLock;
2351
2352    //{{{ getListener() method
2353
private BufferListener getListener(int index)
2354    {
2355        return bufferListeners.get(index).listener;
2356    } //}}}
2357

2358    //{{{ contentInserted() method
2359
private void contentInserted(int offset, int length,
2360        IntegerArray endOffsets)
2361    {
2362        try
2363        {
2364            transaction = true;
2365
2366            int startLine = lineMgr.getLineOfOffset(offset);
2367            int numLines = endOffsets.getSize();
2368
2369            lineMgr.contentInserted(startLine,offset,numLines,length,
2370                endOffsets);
2371            positionMgr.contentInserted(offset,length);
2372
2373            setDirty(true);
2374
2375            if(!loading)
2376            {
2377                fireContentInserted(startLine,offset,numLines,length);
2378
2379                if(!undoInProgress && !insideCompoundEdit())
2380                    fireTransactionComplete();
2381            }
2382
2383        }
2384        finally
2385        {
2386            transaction = false;
2387        }
2388    } //}}}
2389

2390    //{{{ parseBufferLocalProperties() method
2391
private void parseBufferLocalProperties(String JavaDoc prop)
2392    {
2393        StringBuilder JavaDoc buf = new StringBuilder JavaDoc();
2394        String JavaDoc name = null;
2395        boolean escape = false;
2396        for(int i = 0; i < prop.length(); i++)
2397        {
2398            char c = prop.charAt(i);
2399            switch(c)
2400            {
2401            case ':':
2402                if(escape)
2403                {
2404                    escape = false;
2405                    buf.append(':');
2406                    break;
2407                }
2408                if(name != null)
2409                {
2410                    // use the low-level property setting code
2411
// so that if we have a buffer-local
2412
// property with the same value as a default,
2413
// later changes in the default don't affect
2414
// the buffer-local property
2415
properties.put(name,new PropValue(buf.toString(),false));
2416                    name = null;
2417                }
2418                buf.setLength(0);
2419                break;
2420            case '=':
2421                if(escape)
2422                {
2423                    escape = false;
2424                    buf.append('=');
2425                    break;
2426                }
2427                name = buf.toString();
2428                buf.setLength(0);
2429                break;
2430            case '\\':
2431                if(escape)
2432                    buf.append('\\');
2433                escape = !escape;
2434                break;
2435            case 'n':
2436                if(escape)
2437                { buf.append('\n');
2438                    escape = false;
2439                    break;
2440                }
2441            case 'r':
2442                if(escape)
2443                { buf.append('\r');
2444                    escape = false;
2445                    break;
2446                }
2447            case 't':
2448                if(escape)
2449                {
2450                    buf.append('\t');
2451                    escape = false;
2452                    break;
2453                }
2454            default:
2455                buf.append(c);
2456                break;
2457            }
2458        }
2459    } //}}}
2460

2461    //{{{ getIndentRules() method
2462
private List<IndentRule> getIndentRules(int line)
2463    {
2464        String JavaDoc modeName = null;
2465        TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
2466        if (ctx != null && ctx.rules != null)
2467            modeName = ctx.rules.getModeName();
2468        if (modeName == null)
2469            modeName = tokenMarker.getMainRuleSet().getModeName();
2470        return jEdit.getMode(modeName).getIndentRules();
2471    } //}}}
2472

2473    //}}}
2474
}
2475
Popular Tags