Please wait. This can take some minutes ...
Many resources are needed to download a project. Please understand that we have to compensate our server costs. Thank you in advance.
Project price only 1 $
You can buy this project and download/modify it how often you want.
org.gjt.sp.jedit.buffer.JEditBuffer Maven / Gradle / Ivy
/*
* JEditBuffer.java - jEdit buffer
* :tabSize=8:indentSize=8:noTabs=false:
* :folding=explicit:collapseFolds=1:
*
* Copyright (C) 1998, 2005 Slava Pestov
* Portions copyright (C) 1999, 2000 mike dillon
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU General Public License
* as published by the Free Software Foundation; either version 2
* of the License, or any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*/
package org.gjt.sp.jedit.buffer;
//{{{ Imports
import org.gjt.sp.jedit.Debug;
import org.gjt.sp.jedit.Mode;
import org.gjt.sp.jedit.TextUtilities;
import org.gjt.sp.jedit.syntax.*;
import org.gjt.sp.jedit.textarea.TextArea;
import org.gjt.sp.util.IntegerArray;
import org.gjt.sp.util.Log;
import org.gjt.sp.util.StandardUtilities;
import javax.swing.*;
import javax.swing.text.Position;
import javax.swing.text.Segment;
import java.awt.*;
import java.util.*;
import java.util.List;
import java.util.concurrent.locks.ReentrantReadWriteLock;
import java.util.regex.Pattern;
//}}}
/**
* A JEditBuffer
represents the contents of an open text
* file as it is maintained in the computer's memory (as opposed to
* how it may be stored on a disk).
*
* This class is partially thread-safe, however you must pay attention to two
* very important guidelines:
*
* Changes to a buffer can only be made from the AWT thread.
* When accessing the buffer from another thread, you must
* grab a read lock if you plan on performing more than one call, to ensure that
* the buffer contents are not changed by the AWT thread for the duration of the
* lock. Only methods whose descriptions specify thread safety can be invoked
* from other threads.
*
*
* @author Slava Pestov
* @version $Id: JEditBuffer.java 13171 2008-08-03 21:22:09Z kpouer $
*
* @since jEdit 4.3pre3
*/
public class JEditBuffer
{
/**
* Line separator property.
*/
public static final String LINESEP = "lineSeparator";
/**
* Character encoding used when loading and saving.
* @since jEdit 3.2pre4
*/
public static final String ENCODING = "encoding";
//{{{ JEditBuffer constructor
public JEditBuffer(Map props)
{
bufferListeners = new Vector();
lock = new ReentrantReadWriteLock();
contentMgr = new ContentManager();
lineMgr = new LineManager();
positionMgr = new PositionManager(this);
undoMgr = new UndoManager(this);
seg = new Segment();
integerArray = new IntegerArray();
propertyLock = new Object();
properties = new HashMap();
//{{{ need to convert entries of 'props' to PropValue instances
Set set = props.entrySet();
for (Map.Entry entry : set)
{
properties.put(entry.getKey(),new PropValue(entry.getValue(),false));
} //}}}
// fill in defaults for these from system properties if the
// corresponding buffer.XXX properties not set
if(getProperty(ENCODING) == null)
properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
if(getProperty(LINESEP) == null)
properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
} //}}}
//{{{ JEditBuffer constructor
/**
* Create a new JEditBuffer.
* It is used by independent textarea only
*/
public JEditBuffer()
{
bufferListeners = new Vector();
lock = new ReentrantReadWriteLock();
contentMgr = new ContentManager();
lineMgr = new LineManager();
positionMgr = new PositionManager(this);
undoMgr = new UndoManager(this);
seg = new Segment();
integerArray = new IntegerArray();
propertyLock = new Object();
properties = new HashMap();
properties.put("wrap",new PropValue("none",false));
properties.put("folding",new PropValue("none",false));
tokenMarker = new TokenMarker();
tokenMarker.addRuleSet(new ParserRuleSet("text","MAIN"));
setTokenMarker(tokenMarker);
loadText(null,null);
// corresponding buffer.XXX properties not set
if(getProperty(ENCODING) == null)
properties.put(ENCODING,new PropValue(System.getProperty("file.encoding"),false));
if(getProperty(LINESEP) == null)
properties.put(LINESEP,new PropValue(System.getProperty("line.separator"),false));
setFoldHandler(new DummyFoldHandler());
} //}}}
//{{{ Flags
//{{{ isDirty() method
/**
* Returns whether there have been unsaved changes to this buffer.
* This method is thread-safe.
*/
public boolean isDirty()
{
return dirty;
} //}}}
//{{{ isLoading() method
public boolean isLoading()
{
return loading;
} //}}}
//{{{ setLoading() method
public void setLoading(boolean loading)
{
this.loading = loading;
} //}}}
//{{{ isPerformingIO() method
/**
* Returns true if the buffer is currently performing I/O.
* This method is thread-safe.
* @since jEdit 2.7pre1
*/
public boolean isPerformingIO()
{
return isLoading() || io;
} //}}}
//{{{ setPerformingIO() method
/**
* Returns true if the buffer is currently performing I/O.
* This method is thread-safe.
* @since jEdit 2.7pre1
*/
public void setPerformingIO(boolean io)
{
this.io = io;
} //}}}
//{{{ isEditable() method
/**
* Returns true if this file is editable, false otherwise. A file may
* become uneditable if it is read only, or if I/O is in progress.
* This method is thread-safe.
* @since jEdit 2.7pre1
*/
public boolean isEditable()
{
return !(isReadOnly() || isPerformingIO());
} //}}}
//{{{ isReadOnly() method
/**
* Returns true if this file is read only, false otherwise.
* This method is thread-safe.
*/
public boolean isReadOnly()
{
return readOnly || readOnlyOverride;
} //}}}
//{{{ setReadOnly() method
/**
* Sets the read only flag.
* @param readOnly The read only flag
*/
public void setReadOnly(boolean readOnly)
{
readOnlyOverride = readOnly;
} //}}}
//{{{ setDirty() method
/**
* Sets the 'dirty' (changed since last save) flag of this buffer.
*/
public void setDirty(boolean d)
{
boolean editable = isEditable();
if(d)
{
if(editable)
dirty = true;
}
else
{
dirty = false;
// fixes dirty flag not being reset on
// save/insert/undo/redo/undo
if(!isUndoInProgress())
{
// this ensures that undo can clear the dirty flag properly
// when all edits up to a save are undone
undoMgr.resetClearDirty();
}
}
} //}}}
//}}}
//{{{ Thread safety
//{{{ readLock() method
/**
* The buffer is guaranteed not to change between calls to
* {@link #readLock()} and {@link #readUnlock()}.
*/
public void readLock()
{
lock.readLock().lock();
} //}}}
//{{{ readUnlock() method
/**
* The buffer is guaranteed not to change between calls to
* {@link #readLock()} and {@link #readUnlock()}.
*/
public void readUnlock()
{
lock.readLock().unlock();
} //}}}
//{{{ writeLock() method
/**
* Attempting to obtain read lock will block between calls to
* {@link #writeLock()} and {@link #writeUnlock()}.
*/
public void writeLock()
{
lock.writeLock().lock();
} //}}}
//{{{ writeUnlock() method
/**
* Attempting to obtain read lock will block between calls to
* {@link #writeLock()} and {@link #writeUnlock()}.
*/
public void writeUnlock()
{
lock.writeLock().unlock();
} //}}}
//}}}
//{{{ Line offset methods
//{{{ getLength() method
/**
* Returns the number of characters in the buffer. This method is thread-safe.
*/
public int getLength()
{
// no need to lock since this just returns a value and that's it
return contentMgr.getLength();
} //}}}
//{{{ getLineCount() method
/**
* Returns the number of physical lines in the buffer.
* This method is thread-safe.
* @since jEdit 3.1pre1
*/
public int getLineCount()
{
// no need to lock since this just returns a value and that's it
return lineMgr.getLineCount();
} //}}}
//{{{ getLineOfOffset() method
/**
* Returns the line containing the specified offset.
* This method is thread-safe.
* @param offset The offset
* @since jEdit 4.0pre1
*/
public int getLineOfOffset(int offset)
{
try
{
readLock();
if(offset < 0 || offset > getLength())
throw new ArrayIndexOutOfBoundsException(offset);
return lineMgr.getLineOfOffset(offset);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getLineStartOffset() method
/**
* Returns the start offset of the specified line.
* This method is thread-safe.
* @param line The line
* @return The start offset of the specified line
* @since jEdit 4.0pre1
*/
public int getLineStartOffset(int line)
{
try
{
readLock();
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
else if(line == 0)
return 0;
return lineMgr.getLineEndOffset(line - 1);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getLineEndOffset() method
/**
* Returns the end offset of the specified line.
* This method is thread-safe.
* @param line The line
* @return The end offset of the specified line
* invalid.
* @since jEdit 4.0pre1
*/
public int getLineEndOffset(int line)
{
try
{
readLock();
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
return lineMgr.getLineEndOffset(line);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getLineLength() method
/**
* Returns the length of the specified line.
* This method is thread-safe.
* @param line The line
* @since jEdit 4.0pre1
*/
public int getLineLength(int line)
{
try
{
readLock();
return getLineEndOffset(line)
- getLineStartOffset(line) - 1;
}
finally
{
readUnlock();
}
} //}}}
//{{{ getPriorNonEmptyLine() method
/**
* Auto indent needs this.
*/
public int getPriorNonEmptyLine(int lineIndex)
{
int returnValue = -1;
if (!mode.getIgnoreWhitespace())
{
return lineIndex - 1;
}
for(int i = lineIndex - 1; i >= 0; i--)
{
getLineText(i,seg);
if(seg.count != 0)
returnValue = i;
for(int j = 0; j < seg.count; j++)
{
char ch = seg.array[seg.offset + j];
if(!Character.isWhitespace(ch))
return i;
}
}
// didn't find a line that contains non-whitespace chars
// so return index of prior whitespace line
return returnValue;
} //}}}
//}}}
//{{{ Text getters and setters
//{{{ getLineText() method
/**
* Returns the text on the specified line.
* This method is thread-safe.
* @param line The line
* @return The text, or null if the line is invalid
* @since jEdit 4.0pre1
*/
public String getLineText(int line)
{
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
try
{
readLock();
int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
int end = lineMgr.getLineEndOffset(line);
return getText(start,end - start - 1);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getLineText() method
/**
* Returns the specified line in a Segment
.
*
* Using a Segment is generally more
* efficient than using a String because it
* results in less memory allocation and array copying.
*
* This method is thread-safe.
*
* @param line The line
* @since jEdit 4.0pre1
*/
public void getLineText(int line, Segment segment)
{
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
try
{
readLock();
int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
int end = lineMgr.getLineEndOffset(line);
getText(start,end - start - 1,segment);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getLineSegment() method
/**
* Returns the text on the specified line.
* This method is thread-safe.
*
* @param line The line index.
* @return The text, or null if the line is invalid
*
* @since jEdit 4.3pre15
*/
public CharSequence getLineSegment(int line)
{
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
try
{
readLock();
int start = line == 0 ? 0 : lineMgr.getLineEndOffset(line - 1);
int end = lineMgr.getLineEndOffset(line);
return getSegment(start,end - start - 1);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getText() method
/**
* Returns the specified text range. This method is thread-safe.
* @param start The start offset
* @param length The number of characters to get
*/
public String getText(int start, int length)
{
try
{
readLock();
if(start < 0 || length < 0
|| start + length > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(start + ":" + length);
return contentMgr.getText(start,length);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getText() method
/**
* Returns the specified text range in a Segment
.
*
* Using a Segment is generally more
* efficient than using a String because it
* results in less memory allocation and array copying.
*
* This method is thread-safe.
*
* @param start The start offset
* @param length The number of characters to get
* @param seg The segment to copy the text to
*/
public void getText(int start, int length, Segment seg)
{
try
{
readLock();
if(start < 0 || length < 0
|| start + length > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(start + ":" + length);
contentMgr.getText(start,length,seg);
}
finally
{
readUnlock();
}
} //}}}
//{{{ getSegment() method
/**
* Returns the specified text range. This method is thread-safe.
*
* @param start The start offset
* @param length The number of characters to get
*
* @since jEdit 4.3pre15
*/
public CharSequence getSegment(int start, int length)
{
try
{
readLock();
if(start < 0 || length < 0
|| start + length > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(start + ":" + length);
return contentMgr.getSegment(start,length);
}
finally
{
readUnlock();
}
} //}}}
//{{{ insert() method
/**
* Inserts a string into the buffer.
* @param offset The offset
* @param str The string
* @since jEdit 4.0pre1
*/
public void insert(int offset, String str)
{
if(str == null)
return;
int len = str.length();
if(len == 0)
return;
if(isReadOnly())
throw new RuntimeException("buffer read-only");
try
{
writeLock();
if(offset < 0 || offset > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(offset);
contentMgr.insert(offset,str);
integerArray.clear();
for(int i = 0; i < len; i++)
{
if(str.charAt(i) == '\n')
integerArray.add(i + 1);
}
if(!undoInProgress)
{
undoMgr.contentInserted(offset,len,str,!dirty);
}
contentInserted(offset,len,integerArray);
}
finally
{
writeUnlock();
}
} //}}}
//{{{ insert() method
/**
* Inserts a string into the buffer.
* @param offset The offset
* @param seg The segment
* @since jEdit 4.0pre1
*/
public void insert(int offset, Segment seg)
{
if(seg.count == 0)
return;
if(isReadOnly())
throw new RuntimeException("buffer read-only");
try
{
writeLock();
if(offset < 0 || offset > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(offset);
contentMgr.insert(offset,seg);
integerArray.clear();
for(int i = 0; i < seg.count; i++)
{
if(seg.array[seg.offset + i] == '\n')
integerArray.add(i + 1);
}
if(!undoInProgress)
{
undoMgr.contentInserted(offset,seg.count,
seg.toString(),!dirty);
}
contentInserted(offset,seg.count,integerArray);
}
finally
{
writeUnlock();
}
} //}}}
//{{{ remove() method
/**
* Removes the specified rang efrom the buffer.
* @param offset The start offset
* @param length The number of characters to remove
*/
public void remove(int offset, int length)
{
if(length == 0)
return;
if(isReadOnly())
throw new RuntimeException("buffer read-only");
try
{
transaction = true;
writeLock();
if(offset < 0 || length < 0
|| offset + length > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(offset + ":" + length);
int startLine = lineMgr.getLineOfOffset(offset);
int endLine = lineMgr.getLineOfOffset(offset + length);
int numLines = endLine - startLine;
if(!undoInProgress && !loading)
{
undoMgr.contentRemoved(offset,length,
getText(offset,length),
!dirty);
}
firePreContentRemoved(startLine,offset,numLines,length);
contentMgr.remove(offset,length);
lineMgr.contentRemoved(startLine,offset,numLines,length);
positionMgr.contentRemoved(offset,length);
fireContentRemoved(startLine,offset,numLines,length);
/* otherwise it will be delivered later */
if(!undoInProgress && !insideCompoundEdit())
fireTransactionComplete();
setDirty(true);
}
finally
{
transaction = false;
writeUnlock();
}
} //}}}
//}}}
//{{{ Indentation
//{{{ removeTrailingWhiteSpace() method
/**
* Removes trailing whitespace from all lines in the specified list.
* @param lines The line numbers
* @since jEdit 3.2pre1
*/
public void removeTrailingWhiteSpace(int[] lines)
{
try
{
beginCompoundEdit();
for(int i = 0; i < lines.length; i++)
{
int pos, lineStart, lineEnd, tail;
getLineText(lines[i],seg);
// blank line
if (seg.count == 0) continue;
lineStart = seg.offset;
lineEnd = seg.offset + seg.count - 1;
for (pos = lineEnd; pos >= lineStart; pos--)
{
if (!Character.isWhitespace(seg.array[pos]))
break;
}
tail = lineEnd - pos;
// no whitespace
if (tail == 0) continue;
remove(getLineEndOffset(lines[i]) - 1 - tail,tail);
}
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ shiftIndentLeft() method
/**
* Shifts the indent of each line in the specified list to the left.
* @param lines The line numbers
* @since jEdit 3.2pre1
*/
public void shiftIndentLeft(int[] lines)
{
int tabSize = getTabSize();
int indentSize = getIndentSize();
boolean noTabs = getBooleanProperty("noTabs");
try
{
beginCompoundEdit();
for(int i = 0; i < lines.length; i++)
{
int lineStart = getLineStartOffset(lines[i]);
CharSequence line = getLineSegment(lines[i]);
int whiteSpace = StandardUtilities
.getLeadingWhiteSpace(line);
if(whiteSpace == 0)
continue;
int whiteSpaceWidth = Math.max(0,StandardUtilities
.getLeadingWhiteSpaceWidth(line,tabSize)
- indentSize);
insert(lineStart + whiteSpace,StandardUtilities
.createWhiteSpace(whiteSpaceWidth,
noTabs ? 0 : tabSize));
remove(lineStart,whiteSpace);
}
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ shiftIndentRight() method
/**
* Shifts the indent of each line in the specified list to the right.
* @param lines The line numbers
* @since jEdit 3.2pre1
*/
public void shiftIndentRight(int[] lines)
{
try
{
beginCompoundEdit();
int tabSize = getTabSize();
int indentSize = getIndentSize();
boolean noTabs = getBooleanProperty("noTabs");
for(int i = 0; i < lines.length; i++)
{
int lineStart = getLineStartOffset(lines[i]);
CharSequence line = getLineSegment(lines[i]);
int whiteSpace = StandardUtilities
.getLeadingWhiteSpace(line);
// silly usability hack
//if(lines.length != 1 && whiteSpace == 0)
// continue;
int whiteSpaceWidth = StandardUtilities
.getLeadingWhiteSpaceWidth(
line,tabSize) + indentSize;
insert(lineStart + whiteSpace,StandardUtilities
.createWhiteSpace(whiteSpaceWidth,
noTabs ? 0 : tabSize));
remove(lineStart,whiteSpace);
}
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ indentLines() method
/**
* Indents all specified lines.
* @param start The first line to indent
* @param end The last line to indent
* @since jEdit 3.1pre3
*/
public void indentLines(int start, int end)
{
try
{
beginCompoundEdit();
for(int i = start; i <= end; i++)
indentLine(i,true);
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ indentLines() method
/**
* Indents all specified lines.
* @param lines The line numbers
* @since jEdit 3.2pre1
*/
public void indentLines(int[] lines)
{
try
{
beginCompoundEdit();
for(int i = 0; i < lines.length; i++)
indentLine(lines[i],true);
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ indentLine() method
/**
* @deprecated Use {@link #indentLine(int,boolean)} instead.
*/
@Deprecated
public boolean indentLine(int lineIndex, boolean canIncreaseIndent,
boolean canDecreaseIndent)
{
return indentLine(lineIndex,canDecreaseIndent);
} //}}}
//{{{ indentLine() method
/**
* Indents the specified line.
* @param lineIndex The line number to indent
* @param canDecreaseIndent If true, the indent can be decreased as a
* result of this. Set this to false for Tab key.
* @return true If indentation took place, false otherwise.
* @since jEdit 4.2pre2
*/
public boolean indentLine(int lineIndex, boolean canDecreaseIndent)
{
int[] whitespaceChars = new int[1];
int currentIndent = getCurrentIndentForLine(lineIndex,
whitespaceChars);
int idealIndent = getIdealIndentForLine(lineIndex);
if(idealIndent == -1 || idealIndent == currentIndent
|| (!canDecreaseIndent && idealIndent < currentIndent))
return false;
// Do it
try
{
beginCompoundEdit();
int start = getLineStartOffset(lineIndex);
remove(start,whitespaceChars[0]);
insert(start,StandardUtilities.createWhiteSpace(
idealIndent, getBooleanProperty("noTabs") ? 0 : getTabSize()));
}
finally
{
endCompoundEdit();
}
return true;
} //}}}
//{{{ getCurrentIndentForLine() method
/**
* Returns the line's current leading indent.
* @param lineIndex The line number
* @param whitespaceChars If this is non-null, the number of whitespace
* characters is stored at the 0 index
* @since jEdit 4.2pre2
*/
public int getCurrentIndentForLine(int lineIndex, int[] whitespaceChars)
{
getLineText(lineIndex,seg);
int tabSize = getTabSize();
int currentIndent = 0;
loop: for(int i = 0; i < seg.count; i++)
{
char c = seg.array[seg.offset + i];
switch(c)
{
case ' ':
currentIndent++;
if(whitespaceChars != null)
whitespaceChars[0]++;
break;
case '\t':
currentIndent += tabSize - (currentIndent
% tabSize);
if(whitespaceChars != null)
whitespaceChars[0]++;
break;
default:
break loop;
}
}
return currentIndent;
} //}}}
//{{{ getIdealIndentForLine() method
/**
* Returns the ideal leading indent for the specified line.
* This will apply the various auto-indent rules.
* @param lineIndex The line number
*/
public int getIdealIndentForLine(int lineIndex)
{
int prevLineIndex = getPriorNonEmptyLine(lineIndex);
int prevPrevLineIndex = prevLineIndex < 0 ? -1
: getPriorNonEmptyLine(prevLineIndex);
int oldIndent = prevLineIndex == -1 ? 0 :
StandardUtilities.getLeadingWhiteSpaceWidth(
getLineSegment(prevLineIndex),
getTabSize());
int newIndent = oldIndent;
if (newIndent < 0)
newIndent = 0;
return newIndent;
} //}}}
//{{{ getVirtualWidth() method
/**
* Returns the virtual column number (taking tabs into account) of the
* specified position.
*
* @param line The line number
* @param column The column number
* @since jEdit 4.1pre1
*/
public int getVirtualWidth(int line, int column)
{
try
{
readLock();
int start = getLineStartOffset(line);
getText(start,column,seg);
return StandardUtilities.getVirtualWidth(seg,getTabSize());
}
finally
{
readUnlock();
}
} //}}}
//{{{ getOffsetOfVirtualColumn() method
/**
* Returns the offset of a virtual column number (taking tabs
* into account) relative to the start of the line in question.
*
* @param line The line number
* @param column The virtual column number
* @param totalVirtualWidth If this array is non-null, the total
* virtual width will be stored in its first location if this method
* returns -1.
*
* @return -1 if the column is out of bounds
*
* @since jEdit 4.1pre1
*/
public int getOffsetOfVirtualColumn(int line, int column,
int[] totalVirtualWidth)
{
try
{
readLock();
getLineText(line,seg);
return StandardUtilities.getOffsetOfVirtualColumn(seg,
getTabSize(),column,totalVirtualWidth);
}
finally
{
readUnlock();
}
} //}}}
//{{{ insertAtColumn() method
/**
* Like the {@link #insert(int,String)} method, but inserts the string at
* the specified virtual column. Inserts spaces as appropriate if
* the line is shorter than the column.
* @param line The line number
* @param col The virtual column number
* @param str The string
*/
public void insertAtColumn(int line, int col, String str)
{
try
{
writeLock();
int[] total = new int[1];
int offset = getOffsetOfVirtualColumn(line,col,total);
if(offset == -1)
{
offset = getLineEndOffset(line) - 1;
str = StandardUtilities.createWhiteSpace(col - total[0],0) + str;
}
else
offset += getLineStartOffset(line);
insert(offset,str);
}
finally
{
writeUnlock();
}
} //}}}
//{{{ insertIndented() method
/**
* Inserts a string into the buffer, indenting each line of the string
* to match the indent of the first line.
*
* @param offset The offset
* @param text The text
*
* @return The number of characters of indent inserted on each new
* line. This is used by the abbreviations code.
*
* @since jEdit 4.2pre14
*/
public int insertIndented(int offset, String text)
{
try
{
beginCompoundEdit();
// obtain the leading indent for later use
int firstLine = getLineOfOffset(offset);
CharSequence lineText = getLineSegment(firstLine);
int leadingIndent
= StandardUtilities.getLeadingWhiteSpaceWidth(
lineText,getTabSize());
String whiteSpace = StandardUtilities.createWhiteSpace(
leadingIndent,getBooleanProperty("noTabs")
? 0 : getTabSize());
insert(offset,text);
int lastLine = getLineOfOffset(offset + text.length());
// note that if firstLine == lastLine, loop does not
// execute
for(int i = firstLine + 1; i <= lastLine; i++)
{
insert(getLineStartOffset(i),whiteSpace);
}
return whiteSpace.length();
}
finally
{
endCompoundEdit();
}
} //}}}
//{{{ isElectricKey() method
/**
* Should inserting this character trigger a re-indent of
* the current line?
* @since jEdit 4.3pre2
* @deprecated Use #isElectricKey(char,int)
*/
public boolean isElectricKey(char ch)
{
return mode.isElectricKey(ch);
} //}}}
//{{{ isElectricKey() method
/**
* Should inserting this character trigger a re-indent of
* the current line?
* @since jEdit 4.3pre9
*/
public boolean isElectricKey(char ch, int line)
{
TokenMarker.LineContext ctx = lineMgr.getLineContext(line);
Mode mode = ModeProvider.instance.getMode(ctx.rules.getModeName());
// mode can be null, though that's probably an error "further up":
if (mode == null)
return false;
return mode.isElectricKey(ch);
} //}}}
//}}}
//{{{ Syntax highlighting
//{{{ markTokens() method
/**
* Returns the syntax tokens for the specified line.
* @param lineIndex The line number
* @param tokenHandler The token handler that will receive the syntax
* tokens
* @since jEdit 4.1pre1
*/
public void markTokens(int lineIndex, TokenHandler tokenHandler)
{
Segment seg;
if(SwingUtilities.isEventDispatchThread())
seg = this.seg;
else
seg = new Segment();
if(lineIndex < 0 || lineIndex >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(lineIndex);
int firstInvalidLineContext = lineMgr.getFirstInvalidLineContext();
int start;
if(textMode || firstInvalidLineContext == -1)
{
start = lineIndex;
}
else
{
start = Math.min(firstInvalidLineContext,
lineIndex);
}
if(Debug.TOKEN_MARKER_DEBUG)
Log.log(Log.DEBUG,this,"tokenize from " + start + " to " + lineIndex);
TokenMarker.LineContext oldContext = null;
TokenMarker.LineContext context = null;
for(int i = start; i <= lineIndex; i++)
{
getLineText(i,seg);
oldContext = lineMgr.getLineContext(i);
TokenMarker.LineContext prevContext = (
(i == 0 || textMode) ? null
: lineMgr.getLineContext(i - 1)
);
context = tokenMarker.markTokens(prevContext,
(i == lineIndex ? tokenHandler
: DummyTokenHandler.INSTANCE), seg);
lineMgr.setLineContext(i,context);
}
int lineCount = lineMgr.getLineCount();
if(lineCount - 1 == lineIndex)
lineMgr.setFirstInvalidLineContext(-1);
else if(oldContext != context)
lineMgr.setFirstInvalidLineContext(lineIndex + 1);
else if(firstInvalidLineContext == -1)
/* do nothing */;
else
{
lineMgr.setFirstInvalidLineContext(Math.max(
firstInvalidLineContext,lineIndex + 1));
}
} //}}}
//{{{ getTokenMarker() method
public TokenMarker getTokenMarker()
{
return tokenMarker;
} //}}}
//{{{ setTokenMarker() method
public void setTokenMarker(TokenMarker tokenMarker)
{
TokenMarker oldTokenMarker = this.tokenMarker;
this.tokenMarker = tokenMarker;
// don't do this on initial token marker
if(oldTokenMarker != null && tokenMarker != oldTokenMarker)
{
lineMgr.setFirstInvalidLineContext(0);
}
} //}}}
//{{{ createPosition() method
/**
* Creates a floating position.
* @param offset The offset
*/
public Position createPosition(int offset)
{
try
{
readLock();
if(offset < 0 || offset > contentMgr.getLength())
throw new ArrayIndexOutOfBoundsException(offset);
return positionMgr.createPosition(offset);
}
finally
{
readUnlock();
}
} //}}}
//}}}
//{{{ Property methods
//{{{ propertiesChanged() method
/**
* Reloads settings from the properties. This should be called
* after the syntax
or folding
* buffer-local properties are changed.
*/
public void propertiesChanged()
{
String folding = getStringProperty("folding");
FoldHandler handler = FoldHandler.getFoldHandler(folding);
if(handler != null)
{
setFoldHandler(handler);
}
else
{
if (folding != null)
Log.log(Log.WARNING, this, "invalid 'folding' property: " + folding);
setFoldHandler(new DummyFoldHandler());
}
} //}}}
//{{{ getTabSize() method
/**
* Returns the tab size used in this buffer. This is equivalent
* to calling getProperty("tabSize")
.
* This method is thread-safe.
*/
public int getTabSize()
{
int tabSize = getIntegerProperty("tabSize",8);
if(tabSize <= 0)
return 8;
else
return tabSize;
} //}}}
//{{{ getIndentSize() method
/**
* Returns the indent size used in this buffer. This is equivalent
* to calling getProperty("indentSize")
.
* This method is thread-safe.
* @since jEdit 2.7pre1
*/
public int getIndentSize()
{
int indentSize = getIntegerProperty("indentSize",8);
if(indentSize <= 0)
return 8;
else
return indentSize;
} //}}}
//{{{ getProperty() method
/**
* Returns the value of a buffer-local property.
*
* Using this method is generally discouraged, because it returns an
* Object
which must be cast to another type
* in order to be useful, and this can cause problems if the object
* is of a different type than what the caller expects.
*
* The following methods should be used instead:
*
* {@link #getStringProperty(String)}
* {@link #getBooleanProperty(String)}
* {@link #getIntegerProperty(String,int)}
*
*
* This method is thread-safe.
*
* @param name The property name. For backwards compatibility, this
* is an Object
, not a String
.
*/
public Object getProperty(Object name)
{
synchronized(propertyLock)
{
// First try the buffer-local properties
PropValue o = properties.get(name);
if(o != null)
return o.value;
// For backwards compatibility
if(!(name instanceof String))
return null;
Object retVal = getDefaultProperty((String)name);
if(retVal == null)
return null;
else
{
properties.put(name,new PropValue(retVal,true));
return retVal;
}
}
} //}}}
//{{{ getDefaultProperty() method
public Object getDefaultProperty(String key)
{
return null;
} //}}}
//{{{ setProperty() method
/**
* Sets the value of a buffer-local property.
* @param name The property name
* @param value The property value
* @since jEdit 4.0pre1
*/
public void setProperty(String name, Object value)
{
if(value == null)
properties.remove(name);
else
{
PropValue test = properties.get(name);
if(test == null)
properties.put(name,new PropValue(value,false));
else if(test.value.equals(value))
{
// do nothing
}
else
{
test.value = value;
test.defaultValue = false;
}
}
} //}}}
//{{{ setDefaultProperty() method
public void setDefaultProperty(String name, Object value)
{
properties.put(name,new PropValue(value,true));
} //}}}
//{{{ unsetProperty() method
/**
* Clears the value of a buffer-local property.
* @param name The property name
* @since jEdit 4.0pre1
*/
public void unsetProperty(String name)
{
properties.remove(name);
} //}}}
//{{{ resetCachedProperties() method
public void resetCachedProperties()
{
// Need to reset properties that were cached defaults,
// since the defaults might have changed.
Iterator iter = properties.values().iterator();
while(iter.hasNext())
{
PropValue value = iter.next();
if(value.defaultValue)
iter.remove();
}
} //}}}
//{{{ getStringProperty() method
/**
* Returns the value of a string property. This method is thread-safe.
* @param name The property name
* @since jEdit 4.0pre1
*/
public String getStringProperty(String name)
{
Object obj = getProperty(name);
if(obj != null)
return obj.toString();
else
return null;
} //}}}
//{{{ setStringProperty() method
/**
* Sets a string property.
* @param name The property name
* @param value The value
* @since jEdit 4.0pre1
*/
public void setStringProperty(String name, String value)
{
setProperty(name,value);
} //}}}
//{{{ getBooleanProperty() method
/**
* Returns the value of a boolean property. This method is thread-safe.
* @param name The property name
* @since jEdit 4.0pre1
*/
public boolean getBooleanProperty(String name)
{
Object obj = getProperty(name);
if (obj instanceof Boolean)
return (Boolean)obj;
if ("true".equals(obj) || "on".equals(obj) || "yes".equals(obj))
return true;
return false;
} //}}}
//{{{ setBooleanProperty() method
/**
* Sets a boolean property.
* @param name The property name
* @param value The value
* @since jEdit 4.0pre1
*/
public void setBooleanProperty(String name, boolean value)
{
setProperty(name,value ? Boolean.TRUE : Boolean.FALSE);
} //}}}
//{{{ getIntegerProperty() method
/**
* Returns the value of an integer property. This method is thread-safe.
* @param name The property name
* @since jEdit 4.0pre1
*/
public int getIntegerProperty(String name, int defaultValue)
{
boolean defaultValueFlag;
Object obj;
PropValue value = properties.get(name);
if(value != null)
{
obj = value.value;
defaultValueFlag = value.defaultValue;
}
else
{
obj = getProperty(name);
// will be cached from now on...
defaultValueFlag = true;
}
if(obj == null)
return defaultValue;
else if(obj instanceof Number)
return ((Number)obj).intValue();
else
{
try
{
int returnValue = Integer.parseInt(
obj.toString().trim());
properties.put(name,new PropValue(
returnValue,
defaultValueFlag));
return returnValue;
}
catch(Exception e)
{
return defaultValue;
}
}
} //}}}
//{{{ setIntegerProperty() method
/**
* Sets an integer property.
* @param name The property name
* @param value The value
* @since jEdit 4.0pre1
*/
public void setIntegerProperty(String name, int value)
{
setProperty(name,value);
} //}}}
//{{{ getPatternProperty()
/**
* Returns the value of a property as a regular expression.
* This method is thread-safe.
* @param name The property name
* @param flags Regular expression compilation flags
* @since jEdit 4.3pre5
*/
public Pattern getPatternProperty(String name, int flags) {
synchronized(propertyLock)
{
boolean defaultValueFlag;
Object obj;
PropValue value = properties.get(name);
if(value != null)
{
obj = value.value;
defaultValueFlag = value.defaultValue;
}
else
{
obj = getProperty(name);
// will be cached from now on...
defaultValueFlag = true;
}
if(obj == null)
return null;
else if (obj instanceof Pattern)
return (Pattern) obj;
else
{
Pattern re = Pattern.compile(obj.toString(),flags);
properties.put(name,new PropValue(re,
defaultValueFlag));
return re;
}
}
} //}}}
//{{{ getRuleSetAtOffset() method
/**
* Returns the syntax highlighting ruleset at the specified offset.
* @since jEdit 4.1pre1
*/
public ParserRuleSet getRuleSetAtOffset(int offset)
{
int line = getLineOfOffset(offset);
offset -= getLineStartOffset(line);
if(offset != 0)
offset--;
DefaultTokenHandler tokens = new DefaultTokenHandler();
markTokens(line,tokens);
Token token = TextUtilities.getTokenAtOffset(tokens.getTokens(),offset);
return token.rules;
} //}}}
//{{{ getKeywordMapAtOffset() method
/**
* Returns the syntax highlighting keyword map in effect at the
* specified offset. Used by the Complete Word command to
* complete keywords.
* @param offset The offset
* @since jEdit 4.0pre3
*/
public KeywordMap getKeywordMapAtOffset(int offset)
{
return getRuleSetAtOffset(offset).getKeywords();
} //}}}
//{{{ getContextSensitiveProperty() method
/**
* Some settings, like comment start and end strings, can
* vary between different parts of a buffer (HTML text and inline
* JavaScript, for example).
* @param offset The offset
* @param name The property name
* @since jEdit 4.0pre3
*/
public String getContextSensitiveProperty(int offset, String name)
{
ParserRuleSet rules = getRuleSetAtOffset(offset);
Object value = null;
Map rulesetProps = rules.getProperties();
if(rulesetProps != null)
value = rulesetProps.get(name);
if(value == null)
return null;
else
return String.valueOf(value);
} //}}}
//{{{ getMode() method
/**
* Returns this buffer's edit mode. This method is thread-safe.
*/
public Mode getMode()
{
return mode;
} //}}}
//{{{ setMode() method
/**
* Sets this buffer's edit mode. Note that calling this before a buffer
* is loaded will have no effect; in that case, set the "mode" property
* to the name of the mode. A bit inelegant, I know...
* @param mode The mode name
* @since jEdit 4.2pre1
*/
public void setMode(String mode)
{
setMode(ModeProvider.instance.getMode(mode));
} //}}}
//{{{ setMode() method
/**
* Sets this buffer's edit mode. Note that calling this before a buffer
* is loaded will have no effect; in that case, set the "mode" property
* to the name of the mode. A bit inelegant, I know...
* @param mode The mode
*/
public void setMode(Mode mode)
{
/* This protects against stupid people (like me)
* doing stuff like buffer.setMode(jEdit.getMode(...)); */
if(mode == null)
throw new NullPointerException("Mode must be non-null");
this.mode = mode;
textMode = "text".equals(mode.getName());
setTokenMarker(mode.getTokenMarker());
resetCachedProperties();
propertiesChanged();
} //}}}
//}}}
//{{{ Folding methods
//{{{ isFoldStart() method
/**
* Returns if the specified line begins a fold.
* @since jEdit 3.1pre1
*/
public boolean isFoldStart(int line)
{
return line != getLineCount() - 1
&& getFoldLevel(line) < getFoldLevel(line + 1);
} //}}}
//{{{ isFoldEnd() method
/**
* Returns if the specified line ends a fold.
* @since jEdit 4.2pre5
*/
public boolean isFoldEnd(int line)
{
return line != getLineCount() - 1
&& getFoldLevel(line) > getFoldLevel(line + 1);
} //}}}
//{{{ invalidateCachedFoldLevels() method
/**
* Invalidates all cached fold level information.
* @since jEdit 4.1pre11
*/
public void invalidateCachedFoldLevels()
{
lineMgr.setFirstInvalidFoldLevel(0);
fireFoldLevelChanged(0,getLineCount());
} //}}}
//{{{ getFoldLevel() method
/**
* Returns the fold level of the specified line.
* @param line A physical line index
* @since jEdit 3.1pre1
*/
public int getFoldLevel(int line)
{
if(line < 0 || line >= lineMgr.getLineCount())
throw new ArrayIndexOutOfBoundsException(line);
if(foldHandler instanceof DummyFoldHandler)
return 0;
int firstInvalidFoldLevel = lineMgr.getFirstInvalidFoldLevel();
if(firstInvalidFoldLevel == -1 || line < firstInvalidFoldLevel)
{
return lineMgr.getFoldLevel(line);
}
else
{
if(Debug.FOLD_DEBUG)
Log.log(Log.DEBUG,this,"Invalid fold levels from " + firstInvalidFoldLevel + " to " + line);
int newFoldLevel = 0;
boolean changed = false;
for(int i = firstInvalidFoldLevel; i <= line; i++)
{
newFoldLevel = foldHandler.getFoldLevel(this,i,seg);
if(newFoldLevel != lineMgr.getFoldLevel(i))
{
if(Debug.FOLD_DEBUG)
Log.log(Log.DEBUG,this,i + " fold level changed");
changed = true;
}
lineMgr.setFoldLevel(i,newFoldLevel);
}
if(line == lineMgr.getLineCount() - 1)
lineMgr.setFirstInvalidFoldLevel(-1);
else
lineMgr.setFirstInvalidFoldLevel(line + 1);
if(changed)
{
if(Debug.FOLD_DEBUG)
Log.log(Log.DEBUG,this,"fold level changed: " + firstInvalidFoldLevel + ',' + line);
fireFoldLevelChanged(firstInvalidFoldLevel,line);
}
return newFoldLevel;
}
} //}}}
//{{{ getFoldAtLine() method
/**
* Returns an array. The first element is the start line, the
* second element is the end line, of the fold containing the
* specified line number.
* @param line The line number
* @since jEdit 4.0pre3
*/
public int[] getFoldAtLine(int line)
{
int start, end;
if(isFoldStart(line))
{
start = line;
int foldLevel = getFoldLevel(line);
line++;
while(getFoldLevel(line) > foldLevel)
{
line++;
if(line == getLineCount())
break;
}
end = line - 1;
}
else
{
start = line;
int foldLevel = getFoldLevel(line);
while(getFoldLevel(start) >= foldLevel)
{
if(start == 0)
break;
else
start--;
}
end = line;
while(getFoldLevel(end) >= foldLevel)
{
end++;
if(end == getLineCount())
break;
}
end--;
}
while(getLineLength(end) == 0 && end > start)
end--;
return new int[] { start, end };
} //}}}
//{{{ getFoldHandler() method
/**
* Returns the current buffer's fold handler.
* @since jEdit 4.2pre1
*/
public FoldHandler getFoldHandler()
{
return foldHandler;
} //}}}
//{{{ setFoldHandler() method
/**
* Sets the buffer's fold handler.
* @since jEdit 4.2pre2
*/
public void setFoldHandler(FoldHandler foldHandler)
{
FoldHandler oldFoldHandler = this.foldHandler;
if(foldHandler.equals(oldFoldHandler))
return;
this.foldHandler = foldHandler;
lineMgr.setFirstInvalidFoldLevel(0);
fireFoldHandlerChanged();
} //}}}
//}}}
//{{{ Undo
//{{{ undo() method
/**
* Undoes the most recent edit.
*
* @since jEdit 4.0pre1
*/
public void undo(TextArea textArea)
{
if(undoMgr == null)
return;
if(!isEditable())
{
textArea.getToolkit().beep();
return;
}
try
{
writeLock();
undoInProgress = true;
int caret = undoMgr.undo();
fireTransactionComplete();
}
finally
{
undoInProgress = false;
writeUnlock();
}
} //}}}
//{{{ redo() method
/**
* Redoes the most recently undone edit.
*
* @since jEdit 2.7pre2
*/
public void redo(TextArea textArea)
{
if(undoMgr == null)
return;
if(!isEditable())
{
Toolkit.getDefaultToolkit().beep();
return;
}
try
{
writeLock();
undoInProgress = true;
fireTransactionComplete();
}
finally
{
undoInProgress = false;
writeUnlock();
}
} //}}}
//{{{ isTransactionInProgress() method
/**
* Returns if an undo or compound edit is currently in progress. If this
* method returns true, then eventually a
* {@link org.gjt.sp.jedit.buffer.BufferListener#transactionComplete(JEditBuffer)}
* buffer event will get fired.
* @since jEdit 4.0pre6
*/
public boolean isTransactionInProgress()
{
return transaction || undoInProgress || insideCompoundEdit();
} //}}}
//{{{ beginCompoundEdit() method
/**
* Starts a compound edit. All edits from now on until
* {@link #endCompoundEdit()} are called will be merged
* into one. This can be used to make a complex operation
* undoable in one step. Nested calls to
* {@link #beginCompoundEdit()} behave as expected,
* requiring the same number of {@link #endCompoundEdit()}
* calls to end the edit.
* @see #endCompoundEdit()
*/
public void beginCompoundEdit()
{
try
{
writeLock();
undoMgr.beginCompoundEdit();
}
finally
{
writeUnlock();
}
} //}}}
//{{{ endCompoundEdit() method
/**
* Ends a compound edit. All edits performed since
* {@link #beginCompoundEdit()} was called can now
* be undone in one step by calling {@link #undo(TextArea)}.
* @see #beginCompoundEdit()
*/
public void endCompoundEdit()
{
try
{
writeLock();
undoMgr.endCompoundEdit();
if(!insideCompoundEdit())
fireTransactionComplete();
}
finally
{
writeUnlock();
}
}//}}}
//{{{ insideCompoundEdit() method
/**
* Returns if a compound edit is currently active.
* @since jEdit 3.1pre1
*/
public boolean insideCompoundEdit()
{
return undoMgr.insideCompoundEdit();
} //}}}
//{{{ isUndoInProgress() method
/**
* Returns if an undo or redo is currently being performed.
* @since jEdit 4.3pre3
*/
public boolean isUndoInProgress()
{
return undoInProgress;
} //}}}
//}}}
//{{{ Buffer events
public static final int NORMAL_PRIORITY = 0;
public static final int HIGH_PRIORITY = 1;
static class Listener
{
BufferListener listener;
int priority;
Listener(BufferListener listener, int priority)
{
this.listener = listener;
this.priority = priority;
}
}
//{{{ addBufferListener() method
/**
* Adds a buffer change listener.
* @param listener The listener
* @param priority Listeners with HIGH_PRIORITY get the event before
* listeners with NORMAL_PRIORITY
* @since jEdit 4.3pre3
*/
public void addBufferListener(BufferListener listener,
int priority)
{
Listener l = new Listener(listener,priority);
for(int i = 0; i < bufferListeners.size(); i++)
{
Listener _l = bufferListeners.get(i);
if(_l.priority < priority)
{
bufferListeners.add(i,l);
return;
}
}
bufferListeners.add(l);
} //}}}
//{{{ addBufferListener() method
/**
* Adds a buffer change listener.
* @param listener The listener
* @since jEdit 4.3pre3
*/
public void addBufferListener(BufferListener listener)
{
addBufferListener(listener,NORMAL_PRIORITY);
} //}}}
//{{{ removeBufferListener() method
/**
* Removes a buffer change listener.
* @param listener The listener
* @since jEdit 4.3pre3
*/
public void removeBufferListener(BufferListener listener)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
if(bufferListeners.get(i).listener == listener)
{
bufferListeners.remove(i);
return;
}
}
} //}}}
//{{{ getBufferListeners() method
/**
* Returns an array of registered buffer change listeners.
* @since jEdit 4.3pre3
*/
public BufferListener[] getBufferListeners()
{
BufferListener[] returnValue
= new BufferListener[
bufferListeners.size()];
for(int i = 0; i < returnValue.length; i++)
{
returnValue[i] = bufferListeners.get(i).listener;
}
return returnValue;
} //}}}
//}}}
//{{{ Protected members
protected Mode mode;
protected Segment seg;
protected boolean textMode;
protected UndoManager undoMgr;
//{{{ Event firing methods
//{{{ fireFoldLevelChanged() method
protected void fireFoldLevelChanged(int start, int end)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.foldLevelChanged(this,start,end);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ fireContentInserted() method
protected void fireContentInserted(int startLine, int offset,
int numLines, int length)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.contentInserted(this,startLine,
offset,numLines,length);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ fireContentRemoved() method
protected void fireContentRemoved(int startLine, int offset,
int numLines, int length)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.contentRemoved(this,startLine,
offset,numLines,length);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ firePreContentInserted() method
protected void firePreContentInserted(int startLine, int offset,
int numLines, int length)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.preContentInserted(this,startLine,
offset,numLines,length);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ firePreContentRemoved() method
protected void firePreContentRemoved(int startLine, int offset,
int numLines, int length)
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.preContentRemoved(this,startLine,
offset,numLines,length);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ fireTransactionComplete() method
protected void fireTransactionComplete()
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.transactionComplete(this);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ fireFoldHandlerChanged() method
protected void fireFoldHandlerChanged()
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.foldHandlerChanged(this);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//{{{ fireBufferLoaded() method
protected void fireBufferLoaded()
{
for(int i = 0; i < bufferListeners.size(); i++)
{
BufferListener listener = getListener(i);
try
{
listener.bufferLoaded(this);
}
catch(Throwable t)
{
Log.log(Log.ERROR,this,"Exception while sending buffer event to "+ listener +" :");
Log.log(Log.ERROR,this,t);
}
}
} //}}}
//}}}
//{{{ isFileReadOnly() method
protected boolean isFileReadOnly()
{
return readOnly;
} //}}}
//{{{ setFileReadOnly() method
protected void setFileReadOnly(boolean readOnly)
{
this.readOnly = readOnly;
} //}}}
//{{{ loadText() method
protected void loadText(Segment seg, IntegerArray endOffsets)
{
if(seg == null)
seg = new Segment(new char[1024],0,0);
if(endOffsets == null)
{
endOffsets = new IntegerArray();
endOffsets.add(1);
}
try
{
writeLock();
// For `reload' command
// contentMgr.remove() changes this!
int length = getLength();
firePreContentRemoved(0,0,getLineCount()
- 1,length);
contentMgr.remove(0,length);
lineMgr.contentRemoved(0,0,getLineCount()
- 1,length);
positionMgr.contentRemoved(0,length);
fireContentRemoved(0,0,getLineCount()
- 1,length);
firePreContentInserted(0, 0, endOffsets.getSize() - 1, seg.count - 1);
// theoretically a segment could
// have seg.offset != 0 but
// SegmentBuffer never does that
contentMgr._setContent(seg.array,seg.count);
lineMgr._contentInserted(endOffsets);
positionMgr.contentInserted(0,seg.count);
fireContentInserted(0,0,
endOffsets.getSize() - 1,
seg.count - 1);
}
finally
{
writeUnlock();
}
} //}}}
//{{{ invalidateFoldLevels() method
protected void invalidateFoldLevels()
{
lineMgr.setFirstInvalidFoldLevel(0);
} //}}}
//{{{ parseBufferLocalProperties() method
protected void parseBufferLocalProperties()
{
int lastLine = Math.min(9,getLineCount() - 1);
parseBufferLocalProperties(getSegment(0,getLineEndOffset(lastLine) - 1));
// first line for last 10 lines, make sure not to overlap
// with the first 10
int firstLine = Math.max(lastLine + 1, getLineCount() - 10);
if(firstLine < getLineCount())
{
int length = getLineEndOffset(getLineCount() - 1)
- (getLineStartOffset(firstLine) + 1);
parseBufferLocalProperties(getSegment(getLineStartOffset(firstLine),length));
}
} //}}}
//{{{ Used to store property values
protected static class PropValue
{
PropValue(Object value, boolean defaultValue)
{
if(value == null)
throw new NullPointerException();
this.value = value;
this.defaultValue = defaultValue;
}
Object value;
/**
* If this is true, then this value is cached from the mode
* or global defaults, so when the defaults change this property
* value must be reset.
*/
boolean defaultValue;
/**
* For debugging purposes.
*/
public String toString()
{
return value.toString();
}
} //}}}
//}}}
//{{{ Private members
private List bufferListeners;
private final ReentrantReadWriteLock lock;
private ContentManager contentMgr;
private LineManager lineMgr;
private PositionManager positionMgr;
private FoldHandler foldHandler;
private IntegerArray integerArray;
private TokenMarker tokenMarker;
private boolean undoInProgress;
private boolean dirty;
private boolean readOnly;
private boolean readOnlyOverride;
private boolean transaction;
private boolean loading;
private boolean io;
private final Map properties;
private final Object propertyLock;
//{{{ getListener() method
private BufferListener getListener(int index)
{
return bufferListeners.get(index).listener;
} //}}}
//{{{ contentInserted() method
private void contentInserted(int offset, int length,
IntegerArray endOffsets)
{
try
{
transaction = true;
int startLine = lineMgr.getLineOfOffset(offset);
int numLines = endOffsets.getSize();
if (!loading)
{
firePreContentInserted(startLine, offset, numLines, length);
}
lineMgr.contentInserted(startLine,offset,numLines,length,
endOffsets);
positionMgr.contentInserted(offset,length);
setDirty(true);
if(!loading)
{
fireContentInserted(startLine,offset,numLines,length);
if(!undoInProgress && !insideCompoundEdit())
fireTransactionComplete();
}
}
finally
{
transaction = false;
}
} //}}}
//{{{ parseBufferLocalProperties() method
private void parseBufferLocalProperties(CharSequence prop)
{
StringBuilder buf = new StringBuilder();
String name = null;
boolean escape = false;
for(int i = 0; i < prop.length(); i++)
{
char c = prop.charAt(i);
switch(c)
{
case ':':
if(escape)
{
escape = false;
buf.append(':');
break;
}
if(name != null)
{
// use the low-level property setting code
// so that if we have a buffer-local
// property with the same value as a default,
// later changes in the default don't affect
// the buffer-local property
properties.put(name,new PropValue(buf.toString(),false));
name = null;
}
buf.setLength(0);
break;
case '=':
if(escape)
{
escape = false;
buf.append('=');
break;
}
name = buf.toString();
buf.setLength(0);
break;
case '\\':
if(escape)
buf.append('\\');
escape = !escape;
break;
case 'n':
if(escape)
{ buf.append('\n');
escape = false;
break;
}
case 'r':
if(escape)
{ buf.append('\r');
escape = false;
break;
}
case 't':
if(escape)
{
buf.append('\t');
escape = false;
break;
}
default:
buf.append(c);
break;
}
}
} //}}}
}