0001: /*
0002: * JEditBuffer.java - jEdit buffer
0003: * :tabSize=8:indentSize=8:noTabs=false:
0004: * :folding=explicit:collapseFolds=1:
0005: *
0006: * Copyright (C) 1998, 2005 Slava Pestov
0007: * Portions copyright (C) 1999, 2000 mike dillon
0008: *
0009: * This program is free software; you can redistribute it and/or
0010: * modify it under the terms of the GNU General Public License
0011: * as published by the Free Software Foundation; either version 2
0012: * of the License, or any later version.
0013: *
0014: * This program is distributed in the hope that it will be useful,
0015: * but WITHOUT ANY WARRANTY; without even the implied warranty of
0016: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
0017: * GNU General Public License for more details.
0018: *
0019: * You should have received a copy of the GNU General Public License
0020: * along with this program; if not, write to the Free Software
0021: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
0022: */
0023:
0024: package org.gjt.sp.jedit.buffer;
0025:
0026: //{{{ Imports
0027: import org.gjt.sp.jedit.Debug;
0028: import org.gjt.sp.jedit.Mode;
0029: import org.gjt.sp.jedit.TextUtilities;
0030: import org.gjt.sp.jedit.indent.IndentAction;
0031: import org.gjt.sp.jedit.indent.IndentRule;
0032: import org.gjt.sp.jedit.syntax.*;
0033: import org.gjt.sp.jedit.textarea.TextArea;
0034: import org.gjt.sp.util.IntegerArray;
0035: import org.gjt.sp.util.Log;
0036: import org.gjt.sp.util.StandardUtilities;
0037:
0038: import javax.swing.*;
0039: import javax.swing.text.Position;
0040: import javax.swing.text.Segment;
0041: import java.awt.*;
0042: import java.util.*;
0043: import java.util.List;
0044: import java.util.concurrent.locks.ReentrantReadWriteLock;
0045: import java.util.regex.Pattern;
0046:
0047: //}}}
0048:
0049: /**
0050: * A <code>JEditBuffer</code> represents the contents of an open text
0051: * file as it is maintained in the computer's memory (as opposed to
0052: * how it may be stored on a disk).<p>
0053: *
0054: * This class is partially thread-safe, however you must pay attention to two
0055: * very important guidelines:
0056: * <ul>
0057: * <li>Changes to a buffer can only be made from the AWT thread.
0058: * <li>When accessing the buffer from another thread, you must
0059: * grab a read lock if you plan on performing more than one call, to ensure that
0060: * the buffer contents are not changed by the AWT thread for the duration of the
0061: * lock. Only methods whose descriptions specify thread safety can be invoked
0062: * from other threads.
0063: * </ul>
0064: *
0065: * @author Slava Pestov
0066: * @version $Id: JEditBuffer.java 11001 2007-11-08 09:37:38Z kpouer $
0067: *
0068: * @since jEdit 4.3pre3
0069: */
0070: public class JEditBuffer {
0071: /**
0072: * Line separator property.
0073: */
0074: public static final String LINESEP = "lineSeparator";
0075:
0076: /**
0077: * Character encoding used when loading and saving.
0078: * @since jEdit 3.2pre4
0079: */
0080: public static final String ENCODING = "encoding";
0081:
0082: //{{{ JEditBuffer constructor
0083: public JEditBuffer(Map props) {
0084: bufferListeners = new Vector<Listener>();
0085: lock = new ReentrantReadWriteLock();
0086: contentMgr = new ContentManager();
0087: lineMgr = new LineManager();
0088: positionMgr = new PositionManager(this );
0089: undoMgr = new UndoManager(this );
0090: seg = new Segment();
0091: integerArray = new IntegerArray();
0092: propertyLock = new Object();
0093: properties = new HashMap<Object, PropValue>();
0094:
0095: //{{{ need to convert entries of 'props' to PropValue instances
0096: Set<Map.Entry> set = props.entrySet();
0097: for (Map.Entry entry : set) {
0098: properties.put(entry.getKey(), new PropValue(entry
0099: .getValue(), false));
0100: } //}}}
0101:
0102: // fill in defaults for these from system properties if the
0103: // corresponding buffer.XXX properties not set
0104: if (getProperty(ENCODING) == null)
0105: properties.put(ENCODING, new PropValue(System
0106: .getProperty("file.encoding"), false));
0107: if (getProperty(LINESEP) == null)
0108: properties.put(LINESEP, new PropValue(System
0109: .getProperty("line.separator"), false));
0110: } //}}}
0111:
0112: //{{{ JEditBuffer constructor
0113: public JEditBuffer() {
0114: bufferListeners = new Vector<Listener>();
0115: lock = new ReentrantReadWriteLock();
0116: contentMgr = new ContentManager();
0117: lineMgr = new LineManager();
0118: positionMgr = new PositionManager(this );
0119: undoMgr = new UndoManager(this );
0120: seg = new Segment();
0121: integerArray = new IntegerArray();
0122: propertyLock = new Object();
0123: properties = new HashMap<Object, PropValue>();
0124:
0125: properties.put("wrap", new PropValue("none", false));
0126: properties.put("folding", new PropValue("none", false));
0127: TokenMarker tokenMarker = new TokenMarker();
0128: tokenMarker.addRuleSet(new ParserRuleSet("text", "MAIN"));
0129: setTokenMarker(tokenMarker);
0130:
0131: loadText(null, null);
0132: // corresponding buffer.XXX properties not set
0133: if (getProperty(ENCODING) == null)
0134: properties.put(ENCODING, new PropValue(System
0135: .getProperty("file.encoding"), false));
0136: if (getProperty(LINESEP) == null)
0137: properties.put(LINESEP, new PropValue(System
0138: .getProperty("line.separator"), false));
0139:
0140: setFoldHandler(new DummyFoldHandler());
0141: } //}}}
0142:
0143: //{{{ Flags
0144:
0145: //{{{ isDirty() method
0146: /**
0147: * Returns whether there have been unsaved changes to this buffer.
0148: * This method is thread-safe.
0149: */
0150: public boolean isDirty() {
0151: return dirty;
0152: } //}}}
0153:
0154: //{{{ isLoading() method
0155: public boolean isLoading() {
0156: return loading;
0157: } //}}}
0158:
0159: //{{{ setLoading() method
0160: public void setLoading(boolean loading) {
0161: this .loading = loading;
0162: } //}}}
0163:
0164: //{{{ isPerformingIO() method
0165: /**
0166: * Returns true if the buffer is currently performing I/O.
0167: * This method is thread-safe.
0168: * @since jEdit 2.7pre1
0169: */
0170: public boolean isPerformingIO() {
0171: return isLoading() || io;
0172: } //}}}
0173:
0174: //{{{ setPerformingIO() method
0175: /**
0176: * Returns true if the buffer is currently performing I/O.
0177: * This method is thread-safe.
0178: * @since jEdit 2.7pre1
0179: */
0180: public void setPerformingIO(boolean io) {
0181: this .io = io;
0182: } //}}}
0183:
0184: //{{{ isEditable() method
0185: /**
0186: * Returns true if this file is editable, false otherwise. A file may
0187: * become uneditable if it is read only, or if I/O is in progress.
0188: * This method is thread-safe.
0189: * @since jEdit 2.7pre1
0190: */
0191: public boolean isEditable() {
0192: return !(isReadOnly() || isPerformingIO());
0193: } //}}}
0194:
0195: //{{{ isReadOnly() method
0196: /**
0197: * Returns true if this file is read only, false otherwise.
0198: * This method is thread-safe.
0199: */
0200: public boolean isReadOnly() {
0201: return readOnly || readOnlyOverride;
0202: } //}}}
0203:
0204: //{{{ setReadOnly() method
0205: /**
0206: * Sets the read only flag.
0207: * @param readOnly The read only flag
0208: */
0209: public void setReadOnly(boolean readOnly) {
0210: readOnlyOverride = readOnly;
0211: } //}}}
0212:
0213: //{{{ setDirty() method
0214: /**
0215: * Sets the 'dirty' (changed since last save) flag of this buffer.
0216: */
0217: public void setDirty(boolean d) {
0218: boolean editable = isEditable();
0219:
0220: if (d) {
0221: if (editable)
0222: dirty = true;
0223: } else {
0224: dirty = false;
0225:
0226: // fixes dirty flag not being reset on
0227: // save/insert/undo/redo/undo
0228: if (!isUndoInProgress()) {
0229: // this ensures that undo can clear the dirty flag properly
0230: // when all edits up to a save are undone
0231: undoMgr.resetClearDirty();
0232: }
0233: }
0234: } //}}}
0235:
0236: //}}}
0237:
0238: //{{{ Thread safety
0239:
0240: //{{{ readLock() method
0241: /**
0242: * The buffer is guaranteed not to change between calls to
0243: * {@link #readLock()} and {@link #readUnlock()}.
0244: */
0245: public void readLock() {
0246: lock.readLock().lock();
0247: } //}}}
0248:
0249: //{{{ readUnlock() method
0250: /**
0251: * The buffer is guaranteed not to change between calls to
0252: * {@link #readLock()} and {@link #readUnlock()}.
0253: */
0254: public void readUnlock() {
0255: lock.readLock().unlock();
0256: } //}}}
0257:
0258: //{{{ writeLock() method
0259: /**
0260: * Attempting to obtain read lock will block between calls to
0261: * {@link #writeLock()} and {@link #writeUnlock()}.
0262: */
0263: public void writeLock() {
0264: lock.writeLock().lock();
0265: } //}}}
0266:
0267: //{{{ writeUnlock() method
0268: /**
0269: * Attempting to obtain read lock will block between calls to
0270: * {@link #writeLock()} and {@link #writeUnlock()}.
0271: */
0272: public void writeUnlock() {
0273: lock.writeLock().unlock();
0274: } //}}}
0275:
0276: //}}}
0277:
0278: //{{{ Line offset methods
0279:
0280: //{{{ getLength() method
0281: /**
0282: * Returns the number of characters in the buffer. This method is thread-safe.
0283: */
0284: public int getLength() {
0285: // no need to lock since this just returns a value and that's it
0286: return contentMgr.getLength();
0287: } //}}}
0288:
0289: //{{{ getLineCount() method
0290: /**
0291: * Returns the number of physical lines in the buffer.
0292: * This method is thread-safe.
0293: * @since jEdit 3.1pre1
0294: */
0295: public int getLineCount() {
0296: // no need to lock since this just returns a value and that's it
0297: return lineMgr.getLineCount();
0298: } //}}}
0299:
0300: //{{{ getLineOfOffset() method
0301: /**
0302: * Returns the line containing the specified offset.
0303: * This method is thread-safe.
0304: * @param offset The offset
0305: * @since jEdit 4.0pre1
0306: */
0307: public int getLineOfOffset(int offset) {
0308: try {
0309: readLock();
0310:
0311: if (offset < 0 || offset > getLength())
0312: throw new ArrayIndexOutOfBoundsException(offset);
0313:
0314: return lineMgr.getLineOfOffset(offset);
0315: } finally {
0316: readUnlock();
0317: }
0318: } //}}}
0319:
0320: //{{{ getLineStartOffset() method
0321: /**
0322: * Returns the start offset of the specified line.
0323: * This method is thread-safe.
0324: * @param line The line
0325: * @return The start offset of the specified line
0326: * @since jEdit 4.0pre1
0327: */
0328: public int getLineStartOffset(int line) {
0329: try {
0330: readLock();
0331:
0332: if (line < 0 || line >= lineMgr.getLineCount())
0333: throw new ArrayIndexOutOfBoundsException(line);
0334: else if (line == 0)
0335: return 0;
0336:
0337: return lineMgr.getLineEndOffset(line - 1);
0338: } finally {
0339: readUnlock();
0340: }
0341: } //}}}
0342:
0343: //{{{ getLineEndOffset() method
0344: /**
0345: * Returns the end offset of the specified line.
0346: * This method is thread-safe.
0347: * @param line The line
0348: * @return The end offset of the specified line
0349: * invalid.
0350: * @since jEdit 4.0pre1
0351: */
0352: public int getLineEndOffset(int line) {
0353: try {
0354: readLock();
0355:
0356: if (line < 0 || line >= lineMgr.getLineCount())
0357: throw new ArrayIndexOutOfBoundsException(line);
0358:
0359: return lineMgr.getLineEndOffset(line);
0360: } finally {
0361: readUnlock();
0362: }
0363: } //}}}
0364:
0365: //{{{ getLineLength() method
0366: /**
0367: * Returns the length of the specified line.
0368: * This method is thread-safe.
0369: * @param line The line
0370: * @since jEdit 4.0pre1
0371: */
0372: public int getLineLength(int line) {
0373: try {
0374: readLock();
0375:
0376: return getLineEndOffset(line) - getLineStartOffset(line)
0377: - 1;
0378: } finally {
0379: readUnlock();
0380: }
0381: } //}}}
0382:
0383: //{{{ getPriorNonEmptyLine() method
0384: /**
0385: * Auto indent needs this.
0386: */
0387: public int getPriorNonEmptyLine(int lineIndex) {
0388: int returnValue = -1;
0389:
0390: if (!mode.getIgnoreWhitespace()) {
0391: return lineIndex - 1;
0392: }
0393:
0394: for (int i = lineIndex - 1; i >= 0; i--) {
0395: getLineText(i, seg);
0396: if (seg.count != 0)
0397: returnValue = i;
0398: for (int j = 0; j < seg.count; j++) {
0399: char ch = seg.array[seg.offset + j];
0400: if (!Character.isWhitespace(ch))
0401: return i;
0402: }
0403: }
0404:
0405: // didn't find a line that contains non-whitespace chars
0406: // so return index of prior whitespace line
0407: return returnValue;
0408: } //}}}
0409:
0410: //}}}
0411:
0412: //{{{ Text getters and setters
0413:
0414: //{{{ getLineText() method
0415: /**
0416: * Returns the text on the specified line.
0417: * This method is thread-safe.
0418: * @param line The line
0419: * @return The text, or null if the line is invalid
0420: * @since jEdit 4.0pre1
0421: */
0422: public String getLineText(int line) {
0423: if (line < 0 || line >= lineMgr.getLineCount())
0424: throw new ArrayIndexOutOfBoundsException(line);
0425:
0426: try {
0427: readLock();
0428:
0429: int start = line == 0 ? 0 : lineMgr
0430: .getLineEndOffset(line - 1);
0431: int end = lineMgr.getLineEndOffset(line);
0432:
0433: return getText(start, end - start - 1);
0434: } finally {
0435: readUnlock();
0436: }
0437: } //}}}
0438:
0439: //{{{ getLineText() method
0440: /**
0441: * Returns the specified line in a <code>Segment</code>.<p>
0442: *
0443: * Using a <classname>Segment</classname> is generally more
0444: * efficient than using a <classname>String</classname> because it
0445: * results in less memory allocation and array copying.<p>
0446: *
0447: * This method is thread-safe.
0448: *
0449: * @param line The line
0450: * @since jEdit 4.0pre1
0451: */
0452: public void getLineText(int line, Segment segment) {
0453: if (line < 0 || line >= lineMgr.getLineCount())
0454: throw new ArrayIndexOutOfBoundsException(line);
0455:
0456: try {
0457: readLock();
0458:
0459: int start = line == 0 ? 0 : lineMgr
0460: .getLineEndOffset(line - 1);
0461: int end = lineMgr.getLineEndOffset(line);
0462:
0463: getText(start, end - start - 1, segment);
0464: } finally {
0465: readUnlock();
0466: }
0467: } //}}}
0468:
0469: //{{{ getText() method
0470: /**
0471: * Returns the specified text range. This method is thread-safe.
0472: * @param start The start offset
0473: * @param length The number of characters to get
0474: */
0475: public String getText(int start, int length) {
0476: try {
0477: readLock();
0478:
0479: if (start < 0 || length < 0
0480: || start + length > contentMgr.getLength())
0481: throw new ArrayIndexOutOfBoundsException(start + ":"
0482: + length);
0483:
0484: return contentMgr.getText(start, length);
0485: } finally {
0486: readUnlock();
0487: }
0488: } //}}}
0489:
0490: //{{{ getText() method
0491: /**
0492: * Returns the specified text range in a <code>Segment</code>.<p>
0493: *
0494: * Using a <classname>Segment</classname> is generally more
0495: * efficient than using a <classname>String</classname> because it
0496: * results in less memory allocation and array copying.<p>
0497: *
0498: * This method is thread-safe.
0499: *
0500: * @param start The start offset
0501: * @param length The number of characters to get
0502: * @param seg The segment to copy the text to
0503: */
0504: public void getText(int start, int length, Segment seg) {
0505: try {
0506: readLock();
0507:
0508: if (start < 0 || length < 0
0509: || start + length > contentMgr.getLength())
0510: throw new ArrayIndexOutOfBoundsException(start + ":"
0511: + length);
0512:
0513: contentMgr.getText(start, length, seg);
0514: } finally {
0515: readUnlock();
0516: }
0517: } //}}}
0518:
0519: //{{{ insert() method
0520: /**
0521: * Inserts a string into the buffer.
0522: * @param offset The offset
0523: * @param str The string
0524: * @since jEdit 4.0pre1
0525: */
0526: public void insert(int offset, String str) {
0527: if (str == null)
0528: return;
0529:
0530: int len = str.length();
0531:
0532: if (len == 0)
0533: return;
0534:
0535: if (isReadOnly())
0536: throw new RuntimeException("buffer read-only");
0537:
0538: try {
0539: writeLock();
0540:
0541: if (offset < 0 || offset > contentMgr.getLength())
0542: throw new ArrayIndexOutOfBoundsException(offset);
0543:
0544: contentMgr.insert(offset, str);
0545:
0546: integerArray.clear();
0547:
0548: for (int i = 0; i < len; i++) {
0549: if (str.charAt(i) == '\n')
0550: integerArray.add(i + 1);
0551: }
0552:
0553: if (!undoInProgress) {
0554: undoMgr.contentInserted(offset, len, str, !dirty);
0555: }
0556:
0557: contentInserted(offset, len, integerArray);
0558: } finally {
0559: writeUnlock();
0560: }
0561: } //}}}
0562:
0563: //{{{ insert() method
0564: /**
0565: * Inserts a string into the buffer.
0566: * @param offset The offset
0567: * @param seg The segment
0568: * @since jEdit 4.0pre1
0569: */
0570: public void insert(int offset, Segment seg) {
0571: if (seg.count == 0)
0572: return;
0573:
0574: if (isReadOnly())
0575: throw new RuntimeException("buffer read-only");
0576:
0577: try {
0578: writeLock();
0579:
0580: if (offset < 0 || offset > contentMgr.getLength())
0581: throw new ArrayIndexOutOfBoundsException(offset);
0582:
0583: contentMgr.insert(offset, seg);
0584:
0585: integerArray.clear();
0586:
0587: for (int i = 0; i < seg.count; i++) {
0588: if (seg.array[seg.offset + i] == '\n')
0589: integerArray.add(i + 1);
0590: }
0591:
0592: if (!undoInProgress) {
0593: undoMgr.contentInserted(offset, seg.count, seg
0594: .toString(), !dirty);
0595: }
0596:
0597: contentInserted(offset, seg.count, integerArray);
0598: } finally {
0599: writeUnlock();
0600: }
0601: } //}}}
0602:
0603: //{{{ remove() method
0604: /**
0605: * Removes the specified rang efrom the buffer.
0606: * @param offset The start offset
0607: * @param length The number of characters to remove
0608: */
0609: public void remove(int offset, int length) {
0610: if (length == 0)
0611: return;
0612:
0613: if (isReadOnly())
0614: throw new RuntimeException("buffer read-only");
0615:
0616: try {
0617: transaction = true;
0618:
0619: writeLock();
0620:
0621: if (offset < 0 || length < 0
0622: || offset + length > contentMgr.getLength())
0623: throw new ArrayIndexOutOfBoundsException(offset + ":"
0624: + length);
0625:
0626: int startLine = lineMgr.getLineOfOffset(offset);
0627: int endLine = lineMgr.getLineOfOffset(offset + length);
0628:
0629: int numLines = endLine - startLine;
0630:
0631: if (!undoInProgress && !loading) {
0632: undoMgr.contentRemoved(offset, length, getText(offset,
0633: length), !dirty);
0634: }
0635:
0636: firePreContentRemoved(startLine, offset, numLines, length);
0637:
0638: contentMgr.remove(offset, length);
0639: lineMgr.contentRemoved(startLine, offset, numLines, length);
0640: positionMgr.contentRemoved(offset, length);
0641:
0642: fireContentRemoved(startLine, offset, numLines, length);
0643:
0644: /* otherwise it will be delivered later */
0645: if (!undoInProgress && !insideCompoundEdit())
0646: fireTransactionComplete();
0647:
0648: setDirty(true);
0649: } finally {
0650: transaction = false;
0651:
0652: writeUnlock();
0653: }
0654: } //}}}
0655:
0656: //}}}
0657:
0658: //{{{ Indentation
0659:
0660: //{{{ removeTrailingWhiteSpace() method
0661: /**
0662: * Removes trailing whitespace from all lines in the specified list.
0663: * @param lines The line numbers
0664: * @since jEdit 3.2pre1
0665: */
0666: public void removeTrailingWhiteSpace(int[] lines) {
0667: try {
0668: beginCompoundEdit();
0669:
0670: for (int i = 0; i < lines.length; i++) {
0671: int pos, lineStart, lineEnd, tail;
0672:
0673: getLineText(lines[i], seg);
0674:
0675: // blank line
0676: if (seg.count == 0)
0677: continue;
0678:
0679: lineStart = seg.offset;
0680: lineEnd = seg.offset + seg.count - 1;
0681:
0682: for (pos = lineEnd; pos >= lineStart; pos--) {
0683: if (!Character.isWhitespace(seg.array[pos]))
0684: break;
0685: }
0686:
0687: tail = lineEnd - pos;
0688:
0689: // no whitespace
0690: if (tail == 0)
0691: continue;
0692:
0693: remove(getLineEndOffset(lines[i]) - 1 - tail, tail);
0694: }
0695: } finally {
0696: endCompoundEdit();
0697: }
0698: } //}}}
0699:
0700: //{{{ shiftIndentLeft() method
0701: /**
0702: * Shifts the indent of each line in the specified list to the left.
0703: * @param lines The line numbers
0704: * @since jEdit 3.2pre1
0705: */
0706: public void shiftIndentLeft(int[] lines) {
0707: int tabSize = getTabSize();
0708: int indentSize = getIndentSize();
0709: boolean noTabs = getBooleanProperty("noTabs");
0710:
0711: try {
0712: beginCompoundEdit();
0713:
0714: for (int i = 0; i < lines.length; i++) {
0715: int lineStart = getLineStartOffset(lines[i]);
0716: String line = getLineText(lines[i]);
0717: int whiteSpace = StandardUtilities
0718: .getLeadingWhiteSpace(line);
0719: if (whiteSpace == 0)
0720: continue;
0721: int whiteSpaceWidth = Math.max(0, StandardUtilities
0722: .getLeadingWhiteSpaceWidth(line, tabSize)
0723: - indentSize);
0724:
0725: insert(lineStart + whiteSpace, StandardUtilities
0726: .createWhiteSpace(whiteSpaceWidth, noTabs ? 0
0727: : tabSize));
0728: remove(lineStart, whiteSpace);
0729: }
0730:
0731: } finally {
0732: endCompoundEdit();
0733: }
0734: } //}}}
0735:
0736: //{{{ shiftIndentRight() method
0737: /**
0738: * Shifts the indent of each line in the specified list to the right.
0739: * @param lines The line numbers
0740: * @since jEdit 3.2pre1
0741: */
0742: public void shiftIndentRight(int[] lines) {
0743: try {
0744: beginCompoundEdit();
0745:
0746: int tabSize = getTabSize();
0747: int indentSize = getIndentSize();
0748: boolean noTabs = getBooleanProperty("noTabs");
0749: for (int i = 0; i < lines.length; i++) {
0750: int lineStart = getLineStartOffset(lines[i]);
0751: String line = getLineText(lines[i]);
0752: int whiteSpace = StandardUtilities
0753: .getLeadingWhiteSpace(line);
0754:
0755: // silly usability hack
0756: //if(lines.length != 1 && whiteSpace == 0)
0757: // continue;
0758:
0759: int whiteSpaceWidth = StandardUtilities
0760: .getLeadingWhiteSpaceWidth(line, tabSize)
0761: + indentSize;
0762: insert(lineStart + whiteSpace, StandardUtilities
0763: .createWhiteSpace(whiteSpaceWidth, noTabs ? 0
0764: : tabSize));
0765: remove(lineStart, whiteSpace);
0766: }
0767: } finally {
0768: endCompoundEdit();
0769: }
0770: } //}}}
0771:
0772: //{{{ indentLines() method
0773: /**
0774: * Indents all specified lines.
0775: * @param start The first line to indent
0776: * @param end The last line to indent
0777: * @since jEdit 3.1pre3
0778: */
0779: public void indentLines(int start, int end) {
0780: try {
0781: beginCompoundEdit();
0782: for (int i = start; i <= end; i++)
0783: indentLine(i, true);
0784: } finally {
0785: endCompoundEdit();
0786: }
0787: } //}}}
0788:
0789: //{{{ indentLines() method
0790: /**
0791: * Indents all specified lines.
0792: * @param lines The line numbers
0793: * @since jEdit 3.2pre1
0794: */
0795: public void indentLines(int[] lines) {
0796: try {
0797: beginCompoundEdit();
0798: for (int i = 0; i < lines.length; i++)
0799: indentLine(lines[i], true);
0800: } finally {
0801: endCompoundEdit();
0802: }
0803: } //}}}
0804:
0805: //{{{ indentLine() method
0806: /**
0807: * @deprecated Use {@link #indentLine(int,boolean)} instead.
0808: */
0809: public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
0810: boolean canDecreaseIndent) {
0811: return indentLine(lineIndex, canDecreaseIndent);
0812: } //}}}
0813:
0814: //{{{ indentLine() method
0815: /**
0816: * Indents the specified line.
0817: * @param lineIndex The line number to indent
0818: * @param canDecreaseIndent If true, the indent can be decreased as a
0819: * result of this. Set this to false for Tab key.
0820: * @return true If indentation took place, false otherwise.
0821: * @since jEdit 4.2pre2
0822: */
0823: public boolean indentLine(int lineIndex, boolean canDecreaseIndent) {
0824: int[] whitespaceChars = new int[1];
0825: int currentIndent = getCurrentIndentForLine(lineIndex,
0826: whitespaceChars);
0827: int idealIndent = getIdealIndentForLine(lineIndex);
0828:
0829: if (idealIndent == -1 || idealIndent == currentIndent
0830: || (!canDecreaseIndent && idealIndent < currentIndent))
0831: return false;
0832:
0833: // Do it
0834: try {
0835: beginCompoundEdit();
0836:
0837: int start = getLineStartOffset(lineIndex);
0838:
0839: remove(start, whitespaceChars[0]);
0840: insert(start, StandardUtilities.createWhiteSpace(
0841: idealIndent, getBooleanProperty("noTabs") ? 0
0842: : getTabSize()));
0843: } finally {
0844: endCompoundEdit();
0845: }
0846:
0847: return true;
0848: } //}}}
0849:
0850: //{{{ getCurrentIndentForLine() method
0851: /**
0852: * Returns the line's current leading indent.
0853: * @param lineIndex The line number
0854: * @param whitespaceChars If this is non-null, the number of whitespace
0855: * characters is stored at the 0 index
0856: * @since jEdit 4.2pre2
0857: */
0858: public int getCurrentIndentForLine(int lineIndex,
0859: int[] whitespaceChars) {
0860: getLineText(lineIndex, seg);
0861:
0862: int tabSize = getTabSize();
0863:
0864: int currentIndent = 0;
0865: loop: for (int i = 0; i < seg.count; i++) {
0866: char c = seg.array[seg.offset + i];
0867: switch (c) {
0868: case ' ':
0869: currentIndent++;
0870: if (whitespaceChars != null)
0871: whitespaceChars[0]++;
0872: break;
0873: case '\t':
0874: currentIndent += tabSize - (currentIndent % tabSize);
0875: if (whitespaceChars != null)
0876: whitespaceChars[0]++;
0877: break;
0878: default:
0879: break loop;
0880: }
0881: }
0882:
0883: return currentIndent;
0884: } //}}}
0885:
0886: //{{{ getIdealIndentForLine() method
0887: /**
0888: * Returns the ideal leading indent for the specified line.
0889: * This will apply the various auto-indent rules.
0890: * @param lineIndex The line number
0891: */
0892: public int getIdealIndentForLine(int lineIndex) {
0893: int prevLineIndex = getPriorNonEmptyLine(lineIndex);
0894: int prevPrevLineIndex = prevLineIndex < 0 ? -1
0895: : getPriorNonEmptyLine(prevLineIndex);
0896:
0897: int oldIndent = prevLineIndex == -1 ? 0 : StandardUtilities
0898: .getLeadingWhiteSpaceWidth(getLineText(prevLineIndex),
0899: getTabSize());
0900: int newIndent = oldIndent;
0901:
0902: List<IndentRule> indentRules = getIndentRules(lineIndex);
0903: List<IndentAction> actions = new LinkedList<IndentAction>();
0904: for (int i = 0; i < indentRules.size(); i++) {
0905: IndentRule rule = indentRules.get(i);
0906: rule.apply(this , lineIndex, prevLineIndex,
0907: prevPrevLineIndex, actions);
0908: }
0909:
0910: for (IndentAction action : actions) {
0911: newIndent = action.calculateIndent(this , lineIndex,
0912: oldIndent, newIndent);
0913: if (!action.keepChecking())
0914: break;
0915: }
0916: if (newIndent < 0)
0917: newIndent = 0;
0918:
0919: return newIndent;
0920: } //}}}
0921:
0922: //{{{ getVirtualWidth() method
0923: /**
0924: * Returns the virtual column number (taking tabs into account) of the
0925: * specified position.
0926: *
0927: * @param line The line number
0928: * @param column The column number
0929: * @since jEdit 4.1pre1
0930: */
0931: public int getVirtualWidth(int line, int column) {
0932: try {
0933: readLock();
0934:
0935: int start = getLineStartOffset(line);
0936: getText(start, column, seg);
0937:
0938: return StandardUtilities.getVirtualWidth(seg, getTabSize());
0939: } finally {
0940: readUnlock();
0941: }
0942: } //}}}
0943:
0944: //{{{ getOffsetOfVirtualColumn() method
0945: /**
0946: * Returns the offset of a virtual column number (taking tabs
0947: * into account) relative to the start of the line in question.
0948: *
0949: * @param line The line number
0950: * @param column The virtual column number
0951: * @param totalVirtualWidth If this array is non-null, the total
0952: * virtual width will be stored in its first location if this method
0953: * returns -1.
0954: *
0955: * @return -1 if the column is out of bounds
0956: *
0957: * @since jEdit 4.1pre1
0958: */
0959: public int getOffsetOfVirtualColumn(int line, int column,
0960: int[] totalVirtualWidth) {
0961: try {
0962: readLock();
0963:
0964: getLineText(line, seg);
0965:
0966: return StandardUtilities.getOffsetOfVirtualColumn(seg,
0967: getTabSize(), column, totalVirtualWidth);
0968: } finally {
0969: readUnlock();
0970: }
0971: } //}}}
0972:
0973: //{{{ insertAtColumn() method
0974: /**
0975: * Like the {@link #insert(int,String)} method, but inserts the string at
0976: * the specified virtual column. Inserts spaces as appropriate if
0977: * the line is shorter than the column.
0978: * @param line The line number
0979: * @param col The virtual column number
0980: * @param str The string
0981: */
0982: public void insertAtColumn(int line, int col, String str) {
0983: try {
0984: writeLock();
0985:
0986: int[] total = new int[1];
0987: int offset = getOffsetOfVirtualColumn(line, col, total);
0988: if (offset == -1) {
0989: offset = getLineEndOffset(line) - 1;
0990: str = StandardUtilities.createWhiteSpace(
0991: col - total[0], 0)
0992: + str;
0993: } else
0994: offset += getLineStartOffset(line);
0995:
0996: insert(offset, str);
0997: } finally {
0998: writeUnlock();
0999: }
1000: } //}}}
1001:
1002: //{{{ insertIndented() method
1003: /**
1004: * Inserts a string into the buffer, indenting each line of the string
1005: * to match the indent of the first line.
1006: *
1007: * @param offset The offset
1008: * @param text The text
1009: *
1010: * @return The number of characters of indent inserted on each new
1011: * line. This is used by the abbreviations code.
1012: *
1013: * @since jEdit 4.2pre14
1014: */
1015: public int insertIndented(int offset, String text) {
1016: try {
1017: beginCompoundEdit();
1018:
1019: // obtain the leading indent for later use
1020: int firstLine = getLineOfOffset(offset);
1021: String lineText = getLineText(firstLine);
1022: int leadingIndent = StandardUtilities
1023: .getLeadingWhiteSpaceWidth(lineText, getTabSize());
1024:
1025: String whiteSpace = StandardUtilities.createWhiteSpace(
1026: leadingIndent, getBooleanProperty("noTabs") ? 0
1027: : getTabSize());
1028:
1029: insert(offset, text);
1030:
1031: int lastLine = getLineOfOffset(offset + text.length());
1032:
1033: // note that if firstLine == lastLine, loop does not
1034: // execute
1035: for (int i = firstLine + 1; i <= lastLine; i++) {
1036: insert(getLineStartOffset(i), whiteSpace);
1037: }
1038:
1039: return whiteSpace.length();
1040: } finally {
1041: endCompoundEdit();
1042: }
1043: } //}}}
1044:
1045: //{{{ isElectricKey() method
1046: /**
1047: * Should inserting this character trigger a re-indent of
1048: * the current line?
1049: * @since jEdit 4.3pre2
1050: * @deprecated Use #isElectricKey(char,int)
1051: */
1052: public boolean isElectricKey(char ch) {
1053: return mode.isElectricKey(ch);
1054: } //}}}
1055:
1056: //{{{ isElectricKey() method
1057: /**
1058: * Should inserting this character trigger a re-indent of
1059: * the current line?
1060: * @since jEdit 4.3pre9
1061: */
1062: public boolean isElectricKey(char ch, int line) {
1063: TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
1064: Mode mode = ModeProvider.instance.getMode(ctx.rules
1065: .getModeName());
1066:
1067: // mode can be null, though that's probably an error "further up":
1068: if (mode == null)
1069: return false;
1070: return mode.isElectricKey(ch);
1071: } //}}}
1072:
1073: //}}}
1074:
1075: //{{{ Syntax highlighting
1076:
1077: //{{{ markTokens() method
1078: /**
1079: * Returns the syntax tokens for the specified line.
1080: * @param lineIndex The line number
1081: * @param tokenHandler The token handler that will receive the syntax
1082: * tokens
1083: * @since jEdit 4.1pre1
1084: */
1085: public void markTokens(int lineIndex, TokenHandler tokenHandler) {
1086: Segment seg;
1087: if (SwingUtilities.isEventDispatchThread())
1088: seg = this .seg;
1089: else
1090: seg = new Segment();
1091:
1092: if (lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
1093: throw new ArrayIndexOutOfBoundsException(lineIndex);
1094:
1095: int firstInvalidLineContext = lineMgr
1096: .getFirstInvalidLineContext();
1097: int start;
1098: if (textMode || firstInvalidLineContext == -1) {
1099: start = lineIndex;
1100: } else {
1101: start = Math.min(firstInvalidLineContext, lineIndex);
1102: }
1103:
1104: if (Debug.TOKEN_MARKER_DEBUG)
1105: Log.log(Log.DEBUG, this , "tokenize from " + start + " to "
1106: + lineIndex);
1107: TokenMarker.LineContext oldContext = null;
1108: TokenMarker.LineContext context = null;
1109: for (int i = start; i <= lineIndex; i++) {
1110: getLineText(i, seg);
1111:
1112: oldContext = lineMgr.getLineContext(i);
1113:
1114: TokenMarker.LineContext prevContext = ((i == 0 || textMode) ? null
1115: : lineMgr.getLineContext(i - 1));
1116:
1117: context = tokenMarker.markTokens(prevContext,
1118: (i == lineIndex ? tokenHandler
1119: : DummyTokenHandler.INSTANCE), seg);
1120: lineMgr.setLineContext(i, context);
1121: }
1122:
1123: int lineCount = lineMgr.getLineCount();
1124: if (lineCount - 1 == lineIndex)
1125: lineMgr.setFirstInvalidLineContext(-1);
1126: else if (oldContext != context)
1127: lineMgr.setFirstInvalidLineContext(lineIndex + 1);
1128: else if (firstInvalidLineContext == -1)
1129: /* do nothing */;
1130: else {
1131: lineMgr.setFirstInvalidLineContext(Math.max(
1132: firstInvalidLineContext, lineIndex + 1));
1133: }
1134: } //}}}
1135:
1136: //{{{ getTokenMarker() method
1137: public TokenMarker getTokenMarker() {
1138: return tokenMarker;
1139: } //}}}
1140:
1141: //{{{ setTokenMarker() method
1142: public void setTokenMarker(TokenMarker tokenMarker) {
1143: TokenMarker oldTokenMarker = this .tokenMarker;
1144:
1145: this .tokenMarker = tokenMarker;
1146:
1147: // don't do this on initial token marker
1148: if (oldTokenMarker != null && tokenMarker != oldTokenMarker) {
1149: lineMgr.setFirstInvalidLineContext(0);
1150: }
1151: } //}}}
1152:
1153: //{{{ createPosition() method
1154: /**
1155: * Creates a floating position.
1156: * @param offset The offset
1157: */
1158: public Position createPosition(int offset) {
1159: try {
1160: readLock();
1161:
1162: if (offset < 0 || offset > contentMgr.getLength())
1163: throw new ArrayIndexOutOfBoundsException(offset);
1164:
1165: return positionMgr.createPosition(offset);
1166: } finally {
1167: readUnlock();
1168: }
1169: } //}}}
1170:
1171: //}}}
1172:
1173: //{{{ Property methods
1174:
1175: //{{{ propertiesChanged() method
1176: /**
1177: * Reloads settings from the properties. This should be called
1178: * after the <code>syntax</code> or <code>folding</code>
1179: * buffer-local properties are changed.
1180: */
1181: public void propertiesChanged() {
1182: String folding = getStringProperty("folding");
1183: FoldHandler handler = FoldHandler.getFoldHandler(folding);
1184:
1185: if (handler != null) {
1186: setFoldHandler(handler);
1187: } else {
1188: if (folding != null)
1189: Log.log(Log.WARNING, this ,
1190: "invalid 'folding' property: " + folding);
1191: setFoldHandler(new DummyFoldHandler());
1192: }
1193: } //}}}
1194:
1195: //{{{ getTabSize() method
1196: /**
1197: * Returns the tab size used in this buffer. This is equivalent
1198: * to calling <code>getProperty("tabSize")</code>.
1199: * This method is thread-safe.
1200: */
1201: public int getTabSize() {
1202: int tabSize = getIntegerProperty("tabSize", 8);
1203: if (tabSize <= 0)
1204: return 8;
1205: else
1206: return tabSize;
1207: } //}}}
1208:
1209: //{{{ getIndentSize() method
1210: /**
1211: * Returns the indent size used in this buffer. This is equivalent
1212: * to calling <code>getProperty("indentSize")</code>.
1213: * This method is thread-safe.
1214: * @since jEdit 2.7pre1
1215: */
1216: public int getIndentSize() {
1217: int indentSize = getIntegerProperty("indentSize", 8);
1218: if (indentSize <= 0)
1219: return 8;
1220: else
1221: return indentSize;
1222: } //}}}
1223:
1224: //{{{ getProperty() method
1225: /**
1226: * Returns the value of a buffer-local property.<p>
1227: *
1228: * Using this method is generally discouraged, because it returns an
1229: * <code>Object</code> which must be cast to another type
1230: * in order to be useful, and this can cause problems if the object
1231: * is of a different type than what the caller expects.<p>
1232: *
1233: * The following methods should be used instead:
1234: * <ul>
1235: * <li>{@link #getStringProperty(String)}</li>
1236: * <li>{@link #getBooleanProperty(String)}</li>
1237: * <li>{@link #getIntegerProperty(String,int)}</li>
1238: * </ul>
1239: *
1240: * This method is thread-safe.
1241: *
1242: * @param name The property name. For backwards compatibility, this
1243: * is an <code>Object</code>, not a <code>String</code>.
1244: */
1245: public Object getProperty(Object name) {
1246: synchronized (propertyLock) {
1247: // First try the buffer-local properties
1248: PropValue o = properties.get(name);
1249: if (o != null)
1250: return o.value;
1251:
1252: // For backwards compatibility
1253: if (!(name instanceof String))
1254: return null;
1255:
1256: Object retVal = getDefaultProperty((String) name);
1257:
1258: if (retVal == null)
1259: return null;
1260: else {
1261: properties.put(name, new PropValue(retVal, true));
1262: return retVal;
1263: }
1264: }
1265: } //}}}
1266:
1267: //{{{ getDefaultProperty() method
1268: public Object getDefaultProperty(String key) {
1269: return null;
1270: } //}}}
1271:
1272: //{{{ setProperty() method
1273: /**
1274: * Sets the value of a buffer-local property.
1275: * @param name The property name
1276: * @param value The property value
1277: * @since jEdit 4.0pre1
1278: */
1279: public void setProperty(String name, Object value) {
1280: if (value == null)
1281: properties.remove(name);
1282: else {
1283: PropValue test = properties.get(name);
1284: if (test == null)
1285: properties.put(name, new PropValue(value, false));
1286: else if (test.value.equals(value)) {
1287: // do nothing
1288: } else {
1289: test.value = value;
1290: test.defaultValue = false;
1291: }
1292: }
1293: } //}}}
1294:
1295: //{{{ setDefaultProperty() method
1296: public void setDefaultProperty(String name, Object value) {
1297: properties.put(name, new PropValue(value, true));
1298: } //}}}
1299:
1300: //{{{ unsetProperty() method
1301: /**
1302: * Clears the value of a buffer-local property.
1303: * @param name The property name
1304: * @since jEdit 4.0pre1
1305: */
1306: public void unsetProperty(String name) {
1307: properties.remove(name);
1308: } //}}}
1309:
1310: //{{{ resetCachedProperties() method
1311: public void resetCachedProperties() {
1312: // Need to reset properties that were cached defaults,
1313: // since the defaults might have changed.
1314: Iterator<PropValue> iter = properties.values().iterator();
1315: while (iter.hasNext()) {
1316: PropValue value = iter.next();
1317: if (value.defaultValue)
1318: iter.remove();
1319: }
1320: } //}}}
1321:
1322: //{{{ getStringProperty() method
1323: /**
1324: * Returns the value of a string property. This method is thread-safe.
1325: * @param name The property name
1326: * @since jEdit 4.0pre1
1327: */
1328: public String getStringProperty(String name) {
1329: Object obj = getProperty(name);
1330: if (obj != null)
1331: return obj.toString();
1332: else
1333: return null;
1334: } //}}}
1335:
1336: //{{{ setStringProperty() method
1337: /**
1338: * Sets a string property.
1339: * @param name The property name
1340: * @param value The value
1341: * @since jEdit 4.0pre1
1342: */
1343: public void setStringProperty(String name, String value) {
1344: setProperty(name, value);
1345: } //}}}
1346:
1347: //{{{ getBooleanProperty() method
1348: /**
1349: * Returns the value of a boolean property. This method is thread-safe.
1350: * @param name The property name
1351: * @since jEdit 4.0pre1
1352: */
1353: public boolean getBooleanProperty(String name) {
1354: Object obj = getProperty(name);
1355: if (obj instanceof Boolean)
1356: return (Boolean) obj;
1357: else if ("true".equals(obj) || "on".equals(obj)
1358: || "yes".equals(obj))
1359: return true;
1360: else
1361: return false;
1362: } //}}}
1363:
1364: //{{{ setBooleanProperty() method
1365: /**
1366: * Sets a boolean property.
1367: * @param name The property name
1368: * @param value The value
1369: * @since jEdit 4.0pre1
1370: */
1371: public void setBooleanProperty(String name, boolean value) {
1372: setProperty(name, value ? Boolean.TRUE : Boolean.FALSE);
1373: } //}}}
1374:
1375: //{{{ getIntegerProperty() method
1376: /**
1377: * Returns the value of an integer property. This method is thread-safe.
1378: * @param name The property name
1379: * @since jEdit 4.0pre1
1380: */
1381: public int getIntegerProperty(String name, int defaultValue) {
1382: boolean defaultValueFlag;
1383: Object obj;
1384: PropValue value = properties.get(name);
1385: if (value != null) {
1386: obj = value.value;
1387: defaultValueFlag = value.defaultValue;
1388: } else {
1389: obj = getProperty(name);
1390: // will be cached from now on...
1391: defaultValueFlag = true;
1392: }
1393:
1394: if (obj == null)
1395: return defaultValue;
1396: else if (obj instanceof Number)
1397: return ((Number) obj).intValue();
1398: else {
1399: try {
1400: int returnValue = Integer.parseInt(obj.toString()
1401: .trim());
1402: properties.put(name, new PropValue(returnValue,
1403: defaultValueFlag));
1404: return returnValue;
1405: } catch (Exception e) {
1406: return defaultValue;
1407: }
1408: }
1409: } //}}}
1410:
1411: //{{{ setIntegerProperty() method
1412: /**
1413: * Sets an integer property.
1414: * @param name The property name
1415: * @param value The value
1416: * @since jEdit 4.0pre1
1417: */
1418: public void setIntegerProperty(String name, int value) {
1419: setProperty(name, value);
1420: } //}}}
1421:
1422: //{{{ getPatternProperty()
1423: /**
1424: * Returns the value of a property as a regular expression.
1425: * This method is thread-safe.
1426: * @param name The property name
1427: * @param flags Regular expression compilation flags
1428: * @since jEdit 4.3pre5
1429: */
1430: public Pattern getPatternProperty(String name, int flags) {
1431: synchronized (propertyLock) {
1432: boolean defaultValueFlag;
1433: Object obj;
1434: PropValue value = properties.get(name);
1435: if (value != null) {
1436: obj = value.value;
1437: defaultValueFlag = value.defaultValue;
1438: } else {
1439: obj = getProperty(name);
1440: // will be cached from now on...
1441: defaultValueFlag = true;
1442: }
1443:
1444: if (obj == null)
1445: return null;
1446: else if (obj instanceof Pattern)
1447: return (Pattern) obj;
1448: else {
1449: Pattern re = Pattern.compile(obj.toString(), flags);
1450: properties.put(name,
1451: new PropValue(re, defaultValueFlag));
1452: return re;
1453: }
1454: }
1455: } //}}}
1456:
1457: //{{{ getRuleSetAtOffset() method
1458: /**
1459: * Returns the syntax highlighting ruleset at the specified offset.
1460: * @since jEdit 4.1pre1
1461: */
1462: public ParserRuleSet getRuleSetAtOffset(int offset) {
1463: int line = getLineOfOffset(offset);
1464: offset -= getLineStartOffset(line);
1465: if (offset != 0)
1466: offset--;
1467:
1468: DefaultTokenHandler tokens = new DefaultTokenHandler();
1469: markTokens(line, tokens);
1470: Token token = TextUtilities.getTokenAtOffset(
1471: tokens.getTokens(), offset);
1472: return token.rules;
1473: } //}}}
1474:
1475: //{{{ getKeywordMapAtOffset() method
1476: /**
1477: * Returns the syntax highlighting keyword map in effect at the
1478: * specified offset. Used by the <b>Complete Word</b> command to
1479: * complete keywords.
1480: * @param offset The offset
1481: * @since jEdit 4.0pre3
1482: */
1483: public KeywordMap getKeywordMapAtOffset(int offset) {
1484: return getRuleSetAtOffset(offset).getKeywords();
1485: } //}}}
1486:
1487: //{{{ getContextSensitiveProperty() method
1488: /**
1489: * Some settings, like comment start and end strings, can
1490: * vary between different parts of a buffer (HTML text and inline
1491: * JavaScript, for example).
1492: * @param offset The offset
1493: * @param name The property name
1494: * @since jEdit 4.0pre3
1495: */
1496: public String getContextSensitiveProperty(int offset, String name) {
1497: ParserRuleSet rules = getRuleSetAtOffset(offset);
1498:
1499: Object value = null;
1500:
1501: Map<String, String> rulesetProps = rules.getProperties();
1502: if (rulesetProps != null)
1503: value = rulesetProps.get(name);
1504:
1505: if (value == null)
1506: return null;
1507: else
1508: return String.valueOf(value);
1509: } //}}}
1510:
1511: //{{{ getMode() method
1512: /**
1513: * Returns this buffer's edit mode. This method is thread-safe.
1514: */
1515: public Mode getMode() {
1516: return mode;
1517: } //}}}
1518:
1519: //{{{ setMode() method
1520: /**
1521: * Sets this buffer's edit mode. Note that calling this before a buffer
1522: * is loaded will have no effect; in that case, set the "mode" property
1523: * to the name of the mode. A bit inelegant, I know...
1524: * @param mode The mode name
1525: * @since jEdit 4.2pre1
1526: */
1527: public void setMode(String mode) {
1528: setMode(ModeProvider.instance.getMode(mode));
1529: } //}}}
1530:
1531: //{{{ setMode() method
1532: /**
1533: * Sets this buffer's edit mode. Note that calling this before a buffer
1534: * is loaded will have no effect; in that case, set the "mode" property
1535: * to the name of the mode. A bit inelegant, I know...
1536: * @param mode The mode
1537: */
1538: public void setMode(Mode mode) {
1539: /* This protects against stupid people (like me)
1540: * doing stuff like buffer.setMode(jEdit.getMode(...)); */
1541: if (mode == null)
1542: throw new NullPointerException("Mode must be non-null");
1543:
1544: this .mode = mode;
1545:
1546: textMode = "text".equals(mode.getName());
1547:
1548: setTokenMarker(mode.getTokenMarker());
1549:
1550: resetCachedProperties();
1551: propertiesChanged();
1552: } //}}}
1553:
1554: //}}}
1555:
1556: //{{{ Folding methods
1557:
1558: //{{{ isFoldStart() method
1559: /**
1560: * Returns if the specified line begins a fold.
1561: * @since jEdit 3.1pre1
1562: */
1563: public boolean isFoldStart(int line) {
1564: return line != getLineCount() - 1
1565: && getFoldLevel(line) < getFoldLevel(line + 1);
1566: } //}}}
1567:
1568: //{{{ isFoldEnd() method
1569: /**
1570: * Returns if the specified line ends a fold.
1571: * @since jEdit 4.2pre5
1572: */
1573: public boolean isFoldEnd(int line) {
1574: return line != getLineCount() - 1
1575: && getFoldLevel(line) > getFoldLevel(line + 1);
1576: } //}}}
1577:
1578: //{{{ invalidateCachedFoldLevels() method
1579: /**
1580: * Invalidates all cached fold level information.
1581: * @since jEdit 4.1pre11
1582: */
1583: public void invalidateCachedFoldLevels() {
1584: lineMgr.setFirstInvalidFoldLevel(0);
1585: fireFoldLevelChanged(0, getLineCount());
1586: } //}}}
1587:
1588: //{{{ getFoldLevel() method
1589: /**
1590: * Returns the fold level of the specified line.
1591: * @param line A physical line index
1592: * @since jEdit 3.1pre1
1593: */
1594: public int getFoldLevel(int line) {
1595: if (line < 0 || line >= lineMgr.getLineCount())
1596: throw new ArrayIndexOutOfBoundsException(line);
1597:
1598: if (foldHandler instanceof DummyFoldHandler)
1599: return 0;
1600:
1601: int firstInvalidFoldLevel = lineMgr.getFirstInvalidFoldLevel();
1602: if (firstInvalidFoldLevel == -1 || line < firstInvalidFoldLevel) {
1603: return lineMgr.getFoldLevel(line);
1604: } else {
1605: if (Debug.FOLD_DEBUG)
1606: Log.log(Log.DEBUG, this , "Invalid fold levels from "
1607: + firstInvalidFoldLevel + " to " + line);
1608:
1609: int newFoldLevel = 0;
1610: boolean changed = false;
1611:
1612: for (int i = firstInvalidFoldLevel; i <= line; i++) {
1613: newFoldLevel = foldHandler.getFoldLevel(this , i, seg);
1614: if (newFoldLevel != lineMgr.getFoldLevel(i)) {
1615: if (Debug.FOLD_DEBUG)
1616: Log.log(Log.DEBUG, this , i
1617: + " fold level changed");
1618: changed = true;
1619: }
1620: lineMgr.setFoldLevel(i, newFoldLevel);
1621: }
1622:
1623: if (line == lineMgr.getLineCount() - 1)
1624: lineMgr.setFirstInvalidFoldLevel(-1);
1625: else
1626: lineMgr.setFirstInvalidFoldLevel(line + 1);
1627:
1628: if (changed) {
1629: if (Debug.FOLD_DEBUG)
1630: Log.log(Log.DEBUG, this , "fold level changed: "
1631: + firstInvalidFoldLevel + ',' + line);
1632: fireFoldLevelChanged(firstInvalidFoldLevel, line);
1633: }
1634:
1635: return newFoldLevel;
1636: }
1637: } //}}}
1638:
1639: //{{{ getFoldAtLine() method
1640: /**
1641: * Returns an array. The first element is the start line, the
1642: * second element is the end line, of the fold containing the
1643: * specified line number.
1644: * @param line The line number
1645: * @since jEdit 4.0pre3
1646: */
1647: public int[] getFoldAtLine(int line) {
1648: int start, end;
1649:
1650: if (isFoldStart(line)) {
1651: start = line;
1652: int foldLevel = getFoldLevel(line);
1653:
1654: line++;
1655:
1656: while (getFoldLevel(line) > foldLevel) {
1657: line++;
1658:
1659: if (line == getLineCount())
1660: break;
1661: }
1662:
1663: end = line - 1;
1664: } else {
1665: start = line;
1666: int foldLevel = getFoldLevel(line);
1667: while (getFoldLevel(start) >= foldLevel) {
1668: if (start == 0)
1669: break;
1670: else
1671: start--;
1672: }
1673:
1674: end = line;
1675: while (getFoldLevel(end) >= foldLevel) {
1676: end++;
1677:
1678: if (end == getLineCount())
1679: break;
1680: }
1681:
1682: end--;
1683: }
1684:
1685: while (getLineLength(end) == 0 && end > start)
1686: end--;
1687:
1688: return new int[] { start, end };
1689: } //}}}
1690:
1691: //{{{ getFoldHandler() method
1692: /**
1693: * Returns the current buffer's fold handler.
1694: * @since jEdit 4.2pre1
1695: */
1696: public FoldHandler getFoldHandler() {
1697: return foldHandler;
1698: } //}}}
1699:
1700: //{{{ setFoldHandler() method
1701: /**
1702: * Sets the buffer's fold handler.
1703: * @since jEdit 4.2pre2
1704: */
1705: public void setFoldHandler(FoldHandler foldHandler) {
1706: FoldHandler oldFoldHandler = this .foldHandler;
1707:
1708: if (foldHandler.equals(oldFoldHandler))
1709: return;
1710:
1711: this .foldHandler = foldHandler;
1712:
1713: lineMgr.setFirstInvalidFoldLevel(0);
1714:
1715: fireFoldHandlerChanged();
1716: } //}}}
1717:
1718: //}}}
1719:
1720: //{{{ Undo
1721:
1722: //{{{ undo() method
1723: /**
1724: * Undoes the most recent edit.
1725: *
1726: * @since jEdit 4.0pre1
1727: */
1728: public void undo(TextArea textArea) {
1729: if (undoMgr == null)
1730: return;
1731:
1732: if (!isEditable()) {
1733: textArea.getToolkit().beep();
1734: return;
1735: }
1736:
1737: try {
1738: writeLock();
1739:
1740: undoInProgress = true;
1741: int caret = undoMgr.undo();
1742:
1743: if (caret == -1)
1744: textArea.getToolkit().beep();
1745: else
1746: textArea.setCaretPosition(caret);
1747:
1748: fireTransactionComplete();
1749: } finally {
1750: undoInProgress = false;
1751:
1752: writeUnlock();
1753: }
1754: } //}}}
1755:
1756: //{{{ redo() method
1757: /**
1758: * Redoes the most recently undone edit.
1759: *
1760: * @since jEdit 2.7pre2
1761: */
1762: public void redo(TextArea textArea) {
1763: if (undoMgr == null)
1764: return;
1765:
1766: if (!isEditable()) {
1767: Toolkit.getDefaultToolkit().beep();
1768: return;
1769: }
1770:
1771: try {
1772: writeLock();
1773:
1774: undoInProgress = true;
1775: int caret = undoMgr.redo();
1776: if (caret == -1)
1777: textArea.getToolkit().beep();
1778: else
1779: textArea.setCaretPosition(caret);
1780:
1781: fireTransactionComplete();
1782: } finally {
1783: undoInProgress = false;
1784:
1785: writeUnlock();
1786: }
1787: } //}}}
1788:
1789: //{{{ isTransactionInProgress() method
1790: /**
1791: * Returns if an undo or compound edit is currently in progress. If this
1792: * method returns true, then eventually a
1793: * {@link org.gjt.sp.jedit.buffer.BufferListener#transactionComplete(JEditBuffer)}
1794: * buffer event will get fired.
1795: * @since jEdit 4.0pre6
1796: */
1797: public boolean isTransactionInProgress() {
1798: return transaction || undoInProgress || insideCompoundEdit();
1799: } //}}}
1800:
1801: //{{{ beginCompoundEdit() method
1802: /**
1803: * Starts a compound edit. All edits from now on until
1804: * {@link #endCompoundEdit()} are called will be merged
1805: * into one. This can be used to make a complex operation
1806: * undoable in one step. Nested calls to
1807: * {@link #beginCompoundEdit()} behave as expected,
1808: * requiring the same number of {@link #endCompoundEdit()}
1809: * calls to end the edit.
1810: * @see #endCompoundEdit()
1811: */
1812: public void beginCompoundEdit() {
1813: try {
1814: writeLock();
1815:
1816: undoMgr.beginCompoundEdit();
1817: } finally {
1818: writeUnlock();
1819: }
1820: } //}}}
1821:
1822: //{{{ endCompoundEdit() method
1823: /**
1824: * Ends a compound edit. All edits performed since
1825: * {@link #beginCompoundEdit()} was called can now
1826: * be undone in one step by calling {@link #undo(TextArea)}.
1827: * @see #beginCompoundEdit()
1828: */
1829: public void endCompoundEdit() {
1830: try {
1831: writeLock();
1832:
1833: undoMgr.endCompoundEdit();
1834:
1835: if (!insideCompoundEdit())
1836: fireTransactionComplete();
1837: } finally {
1838: writeUnlock();
1839: }
1840: }//}}}
1841:
1842: //{{{ insideCompoundEdit() method
1843: /**
1844: * Returns if a compound edit is currently active.
1845: * @since jEdit 3.1pre1
1846: */
1847: public boolean insideCompoundEdit() {
1848: return undoMgr.insideCompoundEdit();
1849: } //}}}
1850:
1851: //{{{ isUndoInProgress() method
1852: /**
1853: * Returns if an undo or redo is currently being performed.
1854: * @since jEdit 4.3pre3
1855: */
1856: public boolean isUndoInProgress() {
1857: return undoInProgress;
1858: } //}}}
1859:
1860: //}}}
1861:
1862: //{{{ Buffer events
1863: public static final int NORMAL_PRIORITY = 0;
1864: public static final int HIGH_PRIORITY = 1;
1865:
1866: static class Listener {
1867: BufferListener listener;
1868: int priority;
1869:
1870: Listener(BufferListener listener, int priority) {
1871: this .listener = listener;
1872: this .priority = priority;
1873: }
1874: }
1875:
1876: //{{{ addBufferListener() method
1877: /**
1878: * Adds a buffer change listener.
1879: * @param listener The listener
1880: * @param priority Listeners with HIGH_PRIORITY get the event before
1881: * listeners with NORMAL_PRIORITY
1882: * @since jEdit 4.3pre3
1883: */
1884: public void addBufferListener(BufferListener listener, int priority) {
1885: Listener l = new Listener(listener, priority);
1886: for (int i = 0; i < bufferListeners.size(); i++) {
1887: Listener _l = bufferListeners.get(i);
1888: if (_l.priority < priority) {
1889: bufferListeners.add(i, l);
1890: return;
1891: }
1892: }
1893: bufferListeners.add(l);
1894: } //}}}
1895:
1896: //{{{ addBufferListener() method
1897: /**
1898: * Adds a buffer change listener.
1899: * @param listener The listener
1900: * @since jEdit 4.3pre3
1901: */
1902: public void addBufferListener(BufferListener listener) {
1903: addBufferListener(listener, NORMAL_PRIORITY);
1904: } //}}}
1905:
1906: //{{{ removeBufferListener() method
1907: /**
1908: * Removes a buffer change listener.
1909: * @param listener The listener
1910: * @since jEdit 4.3pre3
1911: */
1912: public void removeBufferListener(BufferListener listener) {
1913: for (int i = 0; i < bufferListeners.size(); i++) {
1914: if (bufferListeners.get(i).listener == listener) {
1915: bufferListeners.remove(i);
1916: return;
1917: }
1918: }
1919: } //}}}
1920:
1921: //{{{ getBufferListeners() method
1922: /**
1923: * Returns an array of registered buffer change listeners.
1924: * @since jEdit 4.3pre3
1925: */
1926: public BufferListener[] getBufferListeners() {
1927: BufferListener[] returnValue = new BufferListener[bufferListeners
1928: .size()];
1929: for (int i = 0; i < returnValue.length; i++) {
1930: returnValue[i] = bufferListeners.get(i).listener;
1931: }
1932: return returnValue;
1933: } //}}}
1934:
1935: //}}}
1936:
1937: //{{{ Protected members
1938:
1939: protected Mode mode;
1940: protected Segment seg;
1941: protected boolean textMode;
1942: protected UndoManager undoMgr;
1943:
1944: //{{{ Event firing methods
1945:
1946: //{{{ fireFoldLevelChanged() method
1947: protected void fireFoldLevelChanged(int start, int end) {
1948: for (int i = 0; i < bufferListeners.size(); i++) {
1949: BufferListener listener = getListener(i);
1950: try {
1951: listener.foldLevelChanged(this , start, end);
1952: } catch (Throwable t) {
1953: Log.log(Log.ERROR, this ,
1954: "Exception while sending buffer event to "
1955: + listener + " :");
1956: Log.log(Log.ERROR, this , t);
1957: }
1958: }
1959: } //}}}
1960:
1961: //{{{ fireContentInserted() method
1962: protected void fireContentInserted(int startLine, int offset,
1963: int numLines, int length) {
1964: for (int i = 0; i < bufferListeners.size(); i++) {
1965: BufferListener listener = getListener(i);
1966: try {
1967: listener.contentInserted(this , startLine, offset,
1968: numLines, length);
1969: } catch (Throwable t) {
1970: Log.log(Log.ERROR, this ,
1971: "Exception while sending buffer event to "
1972: + listener + " :");
1973: Log.log(Log.ERROR, this , t);
1974: }
1975: }
1976: } //}}}
1977:
1978: //{{{ fireContentRemoved() method
1979: protected void fireContentRemoved(int startLine, int offset,
1980: int numLines, int length) {
1981: for (int i = 0; i < bufferListeners.size(); i++) {
1982: BufferListener listener = getListener(i);
1983: try {
1984: listener.contentRemoved(this , startLine, offset,
1985: numLines, length);
1986: } catch (Throwable t) {
1987: Log.log(Log.ERROR, this ,
1988: "Exception while sending buffer event to "
1989: + listener + " :");
1990: Log.log(Log.ERROR, this , t);
1991: }
1992: }
1993: } //}}}
1994:
1995: //{{{ fireContentInserted() method
1996: protected void firePreContentInserted(int startLine, int offset,
1997: int numLines, int length) {
1998: for (int i = 0; i < bufferListeners.size(); i++) {
1999: BufferListener listener = getListener(i);
2000: try {
2001: listener.preContentInserted(this , startLine, offset,
2002: numLines, length);
2003: } catch (Throwable t) {
2004: Log.log(Log.ERROR, this ,
2005: "Exception while sending buffer event to "
2006: + listener + " :");
2007: Log.log(Log.ERROR, this , t);
2008: }
2009: }
2010: } //}}}
2011:
2012: //{{{ firePreContentRemoved() method
2013: protected void firePreContentRemoved(int startLine, int offset,
2014: int numLines, int length) {
2015: for (int i = 0; i < bufferListeners.size(); i++) {
2016: BufferListener listener = getListener(i);
2017: try {
2018: listener.preContentRemoved(this , startLine, offset,
2019: numLines, length);
2020: } catch (Throwable t) {
2021: Log.log(Log.ERROR, this ,
2022: "Exception while sending buffer event to "
2023: + listener + " :");
2024: Log.log(Log.ERROR, this , t);
2025: }
2026: }
2027: } //}}}
2028:
2029: //{{{ fireTransactionComplete() method
2030: protected void fireTransactionComplete() {
2031: for (int i = 0; i < bufferListeners.size(); i++) {
2032: BufferListener listener = getListener(i);
2033: try {
2034: listener.transactionComplete(this );
2035: } catch (Throwable t) {
2036: Log.log(Log.ERROR, this ,
2037: "Exception while sending buffer event to "
2038: + listener + " :");
2039: Log.log(Log.ERROR, this , t);
2040: }
2041: }
2042: } //}}}
2043:
2044: //{{{ fireFoldHandlerChanged() method
2045: protected void fireFoldHandlerChanged() {
2046: for (int i = 0; i < bufferListeners.size(); i++) {
2047: BufferListener listener = getListener(i);
2048: try {
2049: listener.foldHandlerChanged(this );
2050: } catch (Throwable t) {
2051: Log.log(Log.ERROR, this ,
2052: "Exception while sending buffer event to "
2053: + listener + " :");
2054: Log.log(Log.ERROR, this , t);
2055: }
2056: }
2057: } //}}}
2058:
2059: //{{{ fireBufferLoaded() method
2060: protected void fireBufferLoaded() {
2061: for (int i = 0; i < bufferListeners.size(); i++) {
2062: BufferListener listener = getListener(i);
2063: try {
2064: listener.bufferLoaded(this );
2065: } catch (Throwable t) {
2066: Log.log(Log.ERROR, this ,
2067: "Exception while sending buffer event to "
2068: + listener + " :");
2069: Log.log(Log.ERROR, this , t);
2070: }
2071: }
2072: } //}}}
2073:
2074: //}}}
2075:
2076: //{{{ isFileReadOnly() method
2077: protected boolean isFileReadOnly() {
2078: return readOnly;
2079: } //}}}
2080:
2081: //{{{ setFileReadOnly() method
2082: protected void setFileReadOnly(boolean readOnly) {
2083: this .readOnly = readOnly;
2084: } //}}}
2085:
2086: //{{{ loadText() method
2087: protected void loadText(Segment seg, IntegerArray endOffsets) {
2088: if (seg == null)
2089: seg = new Segment(new char[1024], 0, 0);
2090:
2091: if (endOffsets == null) {
2092: endOffsets = new IntegerArray();
2093: endOffsets.add(1);
2094: }
2095:
2096: try {
2097: writeLock();
2098:
2099: // For `reload' command
2100: // contentMgr.remove() changes this!
2101: int length = getLength();
2102:
2103: firePreContentRemoved(0, 0, getLineCount() - 1, length);
2104:
2105: contentMgr.remove(0, length);
2106: lineMgr.contentRemoved(0, 0, getLineCount() - 1, length);
2107: positionMgr.contentRemoved(0, length);
2108: fireContentRemoved(0, 0, getLineCount() - 1, length);
2109:
2110: firePreContentInserted(0, 0, endOffsets.getSize() - 1,
2111: seg.count - 1);
2112: // theoretically a segment could
2113: // have seg.offset != 0 but
2114: // SegmentBuffer never does that
2115: contentMgr._setContent(seg.array, seg.count);
2116:
2117: lineMgr._contentInserted(endOffsets);
2118: positionMgr.contentInserted(0, seg.count);
2119:
2120: fireContentInserted(0, 0, endOffsets.getSize() - 1,
2121: seg.count - 1);
2122: } finally {
2123: writeUnlock();
2124: }
2125: } //}}}
2126:
2127: //{{{ invalidateFoldLevels() method
2128: protected void invalidateFoldLevels() {
2129: lineMgr.setFirstInvalidFoldLevel(0);
2130: } //}}}
2131:
2132: //{{{ parseBufferLocalProperties() method
2133: protected void parseBufferLocalProperties() {
2134: int lastLine = Math.min(9, getLineCount() - 1);
2135: parseBufferLocalProperties(getText(0,
2136: getLineEndOffset(lastLine) - 1));
2137:
2138: // first line for last 10 lines, make sure not to overlap
2139: // with the first 10
2140: int firstLine = Math.max(lastLine + 1, getLineCount() - 10);
2141: if (firstLine < getLineCount()) {
2142: int length = getLineEndOffset(getLineCount() - 1)
2143: - (getLineStartOffset(firstLine) + 1);
2144: parseBufferLocalProperties(getText(
2145: getLineStartOffset(firstLine), length));
2146: }
2147: } //}}}
2148:
2149: //{{{ Used to store property values
2150: protected static class PropValue {
2151: PropValue(Object value, boolean defaultValue) {
2152: if (value == null)
2153: throw new NullPointerException();
2154: this .value = value;
2155: this .defaultValue = defaultValue;
2156: }
2157:
2158: Object value;
2159:
2160: /**
2161: * If this is true, then this value is cached from the mode
2162: * or global defaults, so when the defaults change this property
2163: * value must be reset.
2164: */
2165: boolean defaultValue;
2166:
2167: /**
2168: * For debugging purposes.
2169: */
2170: public String toString() {
2171: return value.toString();
2172: }
2173: } //}}}
2174:
2175: //}}}
2176:
2177: //{{{ Private members
2178: private List<Listener> bufferListeners;
2179: private final ReentrantReadWriteLock lock;
2180: private ContentManager contentMgr;
2181: private LineManager lineMgr;
2182: private PositionManager positionMgr;
2183: private FoldHandler foldHandler;
2184: private IntegerArray integerArray;
2185: private TokenMarker tokenMarker;
2186: private boolean undoInProgress;
2187: private boolean dirty;
2188: private boolean readOnly;
2189: private boolean readOnlyOverride;
2190: private boolean transaction;
2191: private boolean loading;
2192: private boolean io;
2193: private final Map<Object, PropValue> properties;
2194: private final Object propertyLock;
2195:
2196: //{{{ getListener() method
2197: private BufferListener getListener(int index) {
2198: return bufferListeners.get(index).listener;
2199: } //}}}
2200:
2201: //{{{ contentInserted() method
2202: private void contentInserted(int offset, int length,
2203: IntegerArray endOffsets) {
2204: try {
2205: transaction = true;
2206:
2207: int startLine = lineMgr.getLineOfOffset(offset);
2208: int numLines = endOffsets.getSize();
2209:
2210: if (!loading) {
2211: firePreContentInserted(startLine, offset, numLines,
2212: length);
2213: }
2214:
2215: lineMgr.contentInserted(startLine, offset, numLines,
2216: length, endOffsets);
2217: positionMgr.contentInserted(offset, length);
2218:
2219: setDirty(true);
2220:
2221: if (!loading) {
2222: fireContentInserted(startLine, offset, numLines, length);
2223:
2224: if (!undoInProgress && !insideCompoundEdit())
2225: fireTransactionComplete();
2226: }
2227:
2228: } finally {
2229: transaction = false;
2230: }
2231: } //}}}
2232:
2233: //{{{ parseBufferLocalProperties() method
2234: private void parseBufferLocalProperties(String prop) {
2235: StringBuilder buf = new StringBuilder();
2236: String name = null;
2237: boolean escape = false;
2238: for (int i = 0; i < prop.length(); i++) {
2239: char c = prop.charAt(i);
2240: switch (c) {
2241: case ':':
2242: if (escape) {
2243: escape = false;
2244: buf.append(':');
2245: break;
2246: }
2247: if (name != null) {
2248: // use the low-level property setting code
2249: // so that if we have a buffer-local
2250: // property with the same value as a default,
2251: // later changes in the default don't affect
2252: // the buffer-local property
2253: properties.put(name, new PropValue(buf.toString(),
2254: false));
2255: name = null;
2256: }
2257: buf.setLength(0);
2258: break;
2259: case '=':
2260: if (escape) {
2261: escape = false;
2262: buf.append('=');
2263: break;
2264: }
2265: name = buf.toString();
2266: buf.setLength(0);
2267: break;
2268: case '\\':
2269: if (escape)
2270: buf.append('\\');
2271: escape = !escape;
2272: break;
2273: case 'n':
2274: if (escape) {
2275: buf.append('\n');
2276: escape = false;
2277: break;
2278: }
2279: case 'r':
2280: if (escape) {
2281: buf.append('\r');
2282: escape = false;
2283: break;
2284: }
2285: case 't':
2286: if (escape) {
2287: buf.append('\t');
2288: escape = false;
2289: break;
2290: }
2291: default:
2292: buf.append(c);
2293: break;
2294: }
2295: }
2296: } //}}}
2297:
2298: //{{{ getIndentRules() method
2299: private List<IndentRule> getIndentRules(int line) {
2300: String modeName = null;
2301: TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
2302: if (ctx != null && ctx.rules != null)
2303: modeName = ctx.rules.getModeName();
2304: if (modeName == null)
2305: modeName = tokenMarker.getMainRuleSet().getModeName();
2306: return ModeProvider.instance.getMode(modeName).getIndentRules();
2307: } //}}}
2308:
2309: //}}}
2310: }
|