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