diff options
Diffstat (limited to 'org.eclipse.text')
40 files changed, 5359 insertions, 0 deletions
diff --git a/org.eclipse.text/.classpath b/org.eclipse.text/.classpath new file mode 100644 index 000000000..d8b3fa0d8 --- /dev/null +++ b/org.eclipse.text/.classpath @@ -0,0 +1,6 @@ +<?xml version="1.0" encoding="UTF-8"?> +<classpath> + <classpathentry kind="src" path="src"/> + <classpathentry kind="var" path="JRE_LIB" rootpath="JRE_SRCROOT" sourcepath="JRE_SRC"/> + <classpathentry kind="output" path="bin"/> +</classpath> diff --git a/org.eclipse.text/.cvsignore b/org.eclipse.text/.cvsignore new file mode 100644 index 000000000..c5e82d745 --- /dev/null +++ b/org.eclipse.text/.cvsignore @@ -0,0 +1 @@ +bin
\ No newline at end of file diff --git a/org.eclipse.text/.project b/org.eclipse.text/.project new file mode 100644 index 000000000..9e0b25af5 --- /dev/null +++ b/org.eclipse.text/.project @@ -0,0 +1,17 @@ +<?xml version="1.0" encoding="UTF-8"?> +<projectDescription> + <name>org.eclipse.text</name> + <comment></comment> + <projects> + </projects> + <buildSpec> + <buildCommand> + <name>org.eclipse.jdt.core.javabuilder</name> + <arguments> + </arguments> + </buildCommand> + </buildSpec> + <natures> + <nature>org.eclipse.jdt.core.javanature</nature> + </natures> +</projectDescription> diff --git a/org.eclipse.text/build.properties b/org.eclipse.text/build.properties new file mode 100644 index 000000000..04e9be9a5 --- /dev/null +++ b/org.eclipse.text/build.properties @@ -0,0 +1,4 @@ +bin.includes = plugin.properties,\ + plugin.xml,\ + *.jar +source.text.jar = src/ diff --git a/org.eclipse.text/plugin.properties b/org.eclipse.text/plugin.properties new file mode 100644 index 000000000..460170dca --- /dev/null +++ b/org.eclipse.text/plugin.properties @@ -0,0 +1,2 @@ +pluginName= Text +providerName= Eclipse.org diff --git a/org.eclipse.text/plugin.xml b/org.eclipse.text/plugin.xml new file mode 100644 index 000000000..ae9d701e3 --- /dev/null +++ b/org.eclipse.text/plugin.xml @@ -0,0 +1,14 @@ +<?xml version="1.0" encoding="UTF-8"?> +<plugin + id="org.eclipse.text" + name="%pluginName" + version="2.1.0" + provider-name="%providerName"> + + <runtime> + <library name="text.jar"> + <export name="*"/> + </library> + </runtime> + +</plugin> diff --git a/org.eclipse.text/scripts/exportplugin.xml b/org.eclipse.text/scripts/exportplugin.xml new file mode 100644 index 000000000..870cc45dc --- /dev/null +++ b/org.eclipse.text/scripts/exportplugin.xml @@ -0,0 +1,28 @@ +<project name="Export Text" default="export" basedir=".."> + <target name="init"> + <tstamp/> + <property name="destdir" value="../../plugin-export" /> + <property name="plugin" value="org.eclipse.text" /> + <property name="version" value="_2.1.0" /> + <property name="dest" value="${destdir}/${plugin}${version}" /> + </target> + + <target name="build" depends="init"> + <eclipse.incrementalBuild project="${plugin}" kind="incr"/> + </target> + + <target name="export" depends="build"> + <mkdir dir="${destdir}" /> + <delete dir="${dest}" /> + <mkdir dir="${dest}" /> + <jar + jarfile="${dest}/text.jar" + basedir="bin" + /> + <copy file="plugin.xml" todir="${dest}"/> + <copy file="plugin.properties" todir="${dest}"/> + <zip zipfile="${dest}/textsrc.zip"> + <fileset dir="src" /> + </zip> + </target> +</project> diff --git a/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java b/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java new file mode 100644 index 000000000..8e822aa7e --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/AbstractDocument.java @@ -0,0 +1,1177 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; +import java.util.List; +import java.util.Map; + + +/** + * Abstract implementation of <code>IDocument</code>. + * Implements the complete contract of <code>IDocument</code> and <code>IDocumentExtension</code>. + * An <code>AbstractDocument</code> supports the following implementation plug-ins: + * <ul> + * <li> a text store for storing and managing the document's content + * <li> a line tracker to map character positions to line numbers and vice versa + * </ul> + * This class must be subclassed. Subclasses must configure which implementation + * plug-ins the document should use. Subclasses are not intended to overwrite existing methods. + * + * @see IDocument + * @see ITextStore + * @see ILineTracker + */ +public abstract class AbstractDocument implements IDocument, IDocumentExtension { + + /** The document's text store */ + private ITextStore fStore; + /** The document's line tracker */ + private ILineTracker fTracker; + /** The document's partitioner */ + private IDocumentPartitioner fDocumentPartitioner; + /** + * The document's partitioner casted to <code>IDocumentPartitionerExtension</code>. + * @since 2.0 + */ + private IDocumentPartitionerExtension fDocumentPartitionerExtension; + /** The registered document listeners */ + private List fDocumentListeners; + /** The registered prenotified document listeners */ + private List fPrenotifiedDocumentListeners; + /** The registered document partitioning listeners */ + private List fDocumentPartitioningListeners; + /** All positions managed by the document */ + private Map fPositions; + /** All registered document position updaters */ + private List fPositionUpdaters; + + /** + * The list of post notification changes + * @since 2.0 + */ + private List fPostNotificationChanges; + /** + * The reentrance count for post notification changes. + * @since 2.0 + */ + private int fReentranceCount= 0; + /** + * Indicates whether post notification change processing has been stopped. + * @since 2.0 + */ + private int fStoppedCount= 0; + + /** + * The default constructor does not perform any configuration + * but leaves it to the clients who must first initialize the + * implementation plug-ins and then call <code>completeInitialization</code>. + * Results in the construction of an empty document. + */ + protected AbstractDocument() { + } + + + //--- accessor to fields ------------------------------- + + /** + * Returns the document's text store. Assumes that the + * document has been initialized with a text store. + * + * @return the document's text store + */ + protected ITextStore getStore() { + Assert.isNotNull(fStore); + return fStore; + } + + /** + * Returns the document's line tracker. Assumes that the + * document has been initialized with a line tracker. + * + * @return the document's line tracker + */ + protected ILineTracker getTracker() { + Assert.isNotNull(fTracker); + return fTracker; + } + + /** + * Returns the document's document listeners. + * + * @return the document's document listeners + */ + protected List getDocumentListeners() { + return fDocumentListeners; + } + + /** + * Returns the document's partitioning listeners . + * + * @return the document's partitioning listeners + */ + protected List getDocumentPartitioningListeners() { + return fDocumentPartitioningListeners; + } + + /** + * Returns all positions managed by the document grouped by category. + * + * @return the document's positions + */ + protected Map getDocumentManagedPositions() { + return fPositions; + } + + /* + * @see IDocument#getDocumentPartitioner + */ + public IDocumentPartitioner getDocumentPartitioner() { + return fDocumentPartitioner; + } + + + + //--- implementation configuration interface ------------ + + /** + * Sets the document's text store. + * Must be called first inside the constructor. + * + * @param store the document's text store + */ + protected void setTextStore(ITextStore store) { + fStore= store; + } + + /** + * Sets the document's line tracker. + * Must be called first inside the constructor. + * + * @param tracker the document's line tracker + */ + protected void setLineTracker(ILineTracker tracker) { + fTracker= tracker; + } + + /* + * @see IDocument#setDocumentPartitioner + */ + public void setDocumentPartitioner(IDocumentPartitioner partitioner) { + fDocumentPartitioner= partitioner; + if (fDocumentPartitioner instanceof IDocumentPartitionerExtension) + fDocumentPartitionerExtension= (IDocumentPartitionerExtension) fDocumentPartitioner; + + fireDocumentPartitioningChanged(new Region(0, getLength())); + } + + /** + * Initializes document listeners, positions, and position updaters. + * Must be called inside the constructor after the implementation plug-ins + * have been set. + */ + protected void completeInitialization() { + + fPositions= new HashMap(); + fPositionUpdaters= new ArrayList(); + fDocumentListeners= new ArrayList(); + fPrenotifiedDocumentListeners= new ArrayList(); + fDocumentPartitioningListeners= new ArrayList(); + + addPositionCategory(DEFAULT_CATEGORY); + addPositionUpdater(new DefaultPositionUpdater(DEFAULT_CATEGORY)); + } + + + //------------------------------------------------------- + + /* + * @see IDocument#addDocumentListener + */ + public void addDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + if (! fDocumentListeners.contains(listener)) + fDocumentListeners.add(listener); + } + + /* + * @see IDocument#removeDocumentListener + */ + public void removeDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + fDocumentListeners.remove(listener); + } + + /* + * @see IDocument#addPrenotifiedDocumentListener(IDocumentListener) + */ + public void addPrenotifiedDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + if (! fPrenotifiedDocumentListeners.contains(listener)) + fPrenotifiedDocumentListeners.add(listener); + } + + /* + * @see IDocument#removePrenotifiedDocumentListener(IDocumentListener) + */ + public void removePrenotifiedDocumentListener(IDocumentListener listener) { + Assert.isNotNull(listener); + fPrenotifiedDocumentListeners.remove(listener); + } + + /* + * @see IDocument#addDocumentPartitioningListener + */ + public void addDocumentPartitioningListener(IDocumentPartitioningListener listener) { + Assert.isNotNull(listener); + if (! fDocumentPartitioningListeners.contains(listener)) + fDocumentPartitioningListeners.add(listener); + } + + /* + * @see IDocument#removeDocumentPartitioningListener + */ + public void removeDocumentPartitioningListener(IDocumentPartitioningListener listener) { + Assert.isNotNull(listener); + fDocumentPartitioningListeners.remove(listener); + } + + /* + * @see IDocument#addPosition + */ + public void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException { + + if ((0 > position.offset) || (0 > position.length) || (position.offset + position.length > getLength())) + throw new BadLocationException(); + + if (category == null) + throw new BadPositionCategoryException(); + + List list= (List) fPositions.get(category); + if (list == null) + throw new BadPositionCategoryException(); + + list.add(computeIndexInPositionList(list, position.offset), position); + } + + /* + * @see IDocument#addPosition + */ + public void addPosition(Position position) throws BadLocationException { + try { + addPosition(DEFAULT_CATEGORY, position); + } catch (BadPositionCategoryException e) { + } + } + + /* + * @see IDocument#addPositionCategory + */ + public void addPositionCategory(String category) { + + if (category == null) + return; + + if (!containsPositionCategory(category)) + fPositions.put(category, new ArrayList()); + } + + /* + * @see IDocument#addPositionUpdater + */ + public void addPositionUpdater(IPositionUpdater updater) { + insertPositionUpdater(updater, fPositionUpdaters.size()); + } + + /* + * @see IDocument#containsPosition + */ + public boolean containsPosition(String category, int offset, int length) { + + if (category == null) + return false; + + List list= (List) fPositions.get(category); + if (list == null) + return false; + + int size= list.size(); + if (size == 0) + return false; + + int index= computeIndexInPositionList(list, offset); + if (index < size) { + Position p= (Position) list.get(index); + while (p != null && p.offset == offset) { + if (p.length == length) + return true; + ++ index; + p= (index < size) ? (Position) list.get(index) : null; + } + } + + return false; + } + + /* + * @see IDocument#containsPositionCategory + */ + public boolean containsPositionCategory(String category) { + if (category != null) + return fPositions.containsKey(category); + return false; + } + + + /** + * Computes the index in the list of positions at which a position with the given + * offset would be inserted. The position is supposed to become the first in this list + * of all positions with the same offset. + * + * @param positions the list in which the index is computed + * @param offset the offset for which the index is computed + * @return the computed index + * + * @see IDocument#computeIndexInCategory(String, int) + */ + protected int computeIndexInPositionList(List positions, int offset) { + + if (positions.size() == 0) + return 0; + + int left= 0; + int right= positions.size() -1; + int mid= 0; + Position p= null; + + while (left < right) { + + mid= (left + right) / 2; + + p= (Position) positions.get(mid); + if (offset < p.getOffset()) { + if (left == mid) + right= left; + else + right= mid -1; + } else if (offset > p.getOffset()) { + if (right == mid) + left= right; + else + left= mid +1; + } else if (offset == p.getOffset()) { + left= right= mid; + } + + } + + int pos= left; + p= (Position) positions.get(pos); + if (offset > p.getOffset()) { + // append to the end + pos++; + } else { + // entry will became the first of all entries with the same offset + do { + --pos; + if (pos < 0) + break; + p= (Position) positions.get(pos); + } while (offset == p.getOffset()); + ++pos; + } + + Assert.isTrue(0 <= pos && pos <= positions.size()); + + return pos; + } + + + /* + * @see IDocument#computeIndexInCategory + */ + public int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException { + + if (0 > offset || offset > getLength()) + throw new BadLocationException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + return computeIndexInPositionList(c, offset); + } + + /** + * Fires the document partitioning changed notification to all registered + * document partitioning listeners. Uses a robust iterator. + * @deprecated use <code>fireDocumentPartitioningChanged(IRegion)</code> instead + */ + protected void fireDocumentPartitioningChanged() { + + if (fDocumentPartitioningListeners != null && fDocumentPartitioningListeners.size() > 0) { + + List list= new ArrayList(fDocumentPartitioningListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentPartitioningListener l= (IDocumentPartitioningListener) e.next(); + l.documentPartitioningChanged(this); + } + } + } + + /** + * Fires the document partitioning changed notification to all registered + * document partitioning listeners. Uses a robust iterator. + * + * @param region the region in which partitioning has changed + * @see IDocumentPartitioningListenerExtension + * @since 2.0 + */ + protected void fireDocumentPartitioningChanged(IRegion region) { + + if (fDocumentPartitioningListeners != null && fDocumentPartitioningListeners.size() > 0) { + + List list= new ArrayList(fDocumentPartitioningListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentPartitioningListener l= (IDocumentPartitioningListener) e.next(); + if (l instanceof IDocumentPartitioningListenerExtension) + ((IDocumentPartitioningListenerExtension) l).documentPartitioningChanged(this, region); + else + l.documentPartitioningChanged(this); + } + } + } + + /** + * Fires the given document event to all registers document listeners informing them + * about the forthcoming document manipulation. Uses a robust iterator. + * + * @param event the event to be sent out + */ + protected void fireDocumentAboutToBeChanged(DocumentEvent event) { + + // IDocumentExtension + if (fReentranceCount == 0) + flushPostNotificationChanges(); + + if (fDocumentPartitioner != null) + fDocumentPartitioner.documentAboutToBeChanged(event); + + if (fPrenotifiedDocumentListeners.size() > 0) { + + List list= new ArrayList(fPrenotifiedDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentAboutToBeChanged(event); + } + } + + if (fDocumentListeners.size() > 0) { + + List list= new ArrayList(fDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentAboutToBeChanged(event); + } + } + } + + /** + * Updates document partitioning and document positions according to the + * specification given by the document event. + * + * @param event the document event describing the change to which structures must be adapted + */ + protected void updateDocumentStructures(DocumentEvent event) { + boolean partitioningChanged= false; + IRegion changedRegion= null; + + if (fDocumentPartitioner != null) { + if (fDocumentPartitionerExtension != null) { + changedRegion= fDocumentPartitionerExtension.documentChanged2(event); + partitioningChanged= (changedRegion != null); + } else + partitioningChanged= fDocumentPartitioner.documentChanged(event); + } + + if (fPositions.size() > 0) + updatePositions(event); + + if (partitioningChanged) + fireDocumentPartitioningChanged(changedRegion); + } + + /** + * Updates the internal document structures and informs all document listeners. + * Uses a robust iterator. <p> + * Executes all registered post notification replace operation. + * + * @param event the document event to be sent out + * @see IDocumentExtension + */ + protected void fireDocumentChanged(DocumentEvent event) { + updateDocumentStructures(event); + + if (fPrenotifiedDocumentListeners.size() > 0) { + + List list= new ArrayList(fPrenotifiedDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentChanged(event); + } + } + + if (fDocumentListeners.size() > 0) { + + List list= new ArrayList(fDocumentListeners); + Iterator e= list.iterator(); + while (e.hasNext()) { + IDocumentListener l= (IDocumentListener) e.next(); + l.documentChanged(event); + } + } + + // IDocumentExtension + ++ fReentranceCount; + try { + if (fReentranceCount == 1) + executePostNotificationChanges(); + } finally { + -- fReentranceCount; + } + } + + /* + * @see IDocument#getChar + */ + public char getChar(int pos) throws BadLocationException { + if ((0 > pos) || (pos >= getLength())) + throw new BadLocationException(); + return getStore().get(pos); + } + + /* + * @see IDocument#getContentType + */ + public String getContentType(int offset) throws BadLocationException { + if ((0 > offset) || (offset > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return DEFAULT_CONTENT_TYPE; + + return fDocumentPartitioner.getContentType(offset); + } + + /* + * @see IDocument#getLegalContentTypes + */ + public String[] getLegalContentTypes() { + if (fDocumentPartitioner == null) + return new String[] { DEFAULT_CONTENT_TYPE }; + return fDocumentPartitioner.getLegalContentTypes(); + } + + /* + * @see IDocument#getLength + */ + public int getLength() { + return getStore().getLength(); + } + + /* + * @see IDocument#getLineDelimiter + */ + public String getLineDelimiter(int line) throws BadLocationException { + return getTracker().getLineDelimiter(line); + } + + /* + * @see IDocument#getLegalLineDelimiters + */ + public String[] getLegalLineDelimiters() { + return getTracker().getLegalLineDelimiters(); + } + + /* + * @see IDocument#getLineLength + */ + public int getLineLength(int line) throws BadLocationException { + return getTracker().getLineLength(line); + } + + /* + * @see IDocument#getLineOfOffset + */ + public int getLineOfOffset(int pos) throws BadLocationException { + return getTracker().getLineNumberOfOffset(pos); + } + + /* + * @see IDocument#getLineOffset + */ + public int getLineOffset(int line) throws BadLocationException { + return getTracker().getLineOffset(line); + } + + /* + * @see IDocument#getLineInformation + */ + public IRegion getLineInformation(int line) throws BadLocationException { + return getTracker().getLineInformation(line); + } + + /* + * @see IDocument#getLineInformationOfOffset + */ + public IRegion getLineInformationOfOffset(int offset) throws BadLocationException { + return getTracker().getLineInformationOfOffset(offset); + } + + /* + * @see IDocument#getNumberOfLines + */ + public int getNumberOfLines() { + return getTracker().getNumberOfLines(); + } + + /* + * @see IDocument#getNumberOfLines(int, int) + */ + public int getNumberOfLines(int offset, int length) throws BadLocationException { + return getTracker().getNumberOfLines(offset, length); + } + + /* + * @see IDocument#computeNumberOfLines(String) + */ + public int computeNumberOfLines(String text) { + return getTracker().computeNumberOfLines(text); + } + + /* + * @see IDocument#getPartition + */ + public ITypedRegion getPartition(int offset) throws BadLocationException { + if ((0 > offset) || (offset > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return new TypedRegion(0, getLength(), DEFAULT_CONTENT_TYPE); + + return fDocumentPartitioner.getPartition(offset); + } + + /* + * @see IDocument#computePartitioning + */ + public ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException { + if ((0 > offset) || (0 > length) || (offset + length > getLength())) + throw new BadLocationException(); + + if (fDocumentPartitioner == null) + return new TypedRegion[] { new TypedRegion(offset, length, DEFAULT_CONTENT_TYPE) }; + + return fDocumentPartitioner.computePartitioning(offset, length); + } + + /* + * @see IDocument#getPositions + */ + public Position[] getPositions(String category) throws BadPositionCategoryException { + + if (category == null) + throw new BadPositionCategoryException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + Position[] positions= new Position[c.size()]; + c.toArray(positions); + return positions; + } + + /* + * @see IDocument#getPositionCategories + */ + public String[] getPositionCategories() { + String[] categories= new String[fPositions.size()]; + Iterator keys= fPositions.keySet().iterator(); + for (int i= 0; i < categories.length; i++) + categories[i]= (String) keys.next(); + return categories; + } + + /* + * @see IDocument#getPositionUpdaters + */ + public IPositionUpdater[] getPositionUpdaters() { + IPositionUpdater[] updaters= new IPositionUpdater[fPositionUpdaters.size()]; + fPositionUpdaters.toArray(updaters); + return updaters; + } + + /* + * @see IDocument#get + */ + public String get() { + return getStore().get(0, getLength()); + } + + /* + * @see IDocument#get + */ + public String get(int pos, int length) throws BadLocationException { + int myLength= getLength(); + if ((0 > pos) || (0 > length) || (pos + length > myLength)) + throw new BadLocationException(); + return getStore().get(pos, length); + } + + /* + * @see IDocument#insertPositionUpdater + */ + public void insertPositionUpdater(IPositionUpdater updater, int index) { + + for (int i= fPositionUpdaters.size() - 1; i >= 0; i--) { + if (fPositionUpdaters.get(i) == updater) + return; + } + + if (index == fPositionUpdaters.size()) + fPositionUpdaters.add(updater); + else + fPositionUpdaters.add(index, updater); + } + + /* + * @see IDocument#removePosition + */ + public void removePosition(String category, Position position) throws BadPositionCategoryException { + + if (position == null) + return; + + if (category == null) + throw new BadPositionCategoryException(); + + List c= (List) fPositions.get(category); + if (c == null) + throw new BadPositionCategoryException(); + + // remove based on identity not equality + int size= c.size(); + for (int i= 0; i < size; i++) { + if (position == c.get(i)) { + c.remove(i); + return; + } + } + } + + /* + * @see IDocument#removePosition + */ + public void removePosition(Position position) { + try { + removePosition(DEFAULT_CATEGORY, position); + } catch (BadPositionCategoryException e) { + } + } + + /* + * @see IDocument#removePositionCategory + */ + public void removePositionCategory(String category) throws BadPositionCategoryException { + + if (category == null) + return; + + if ( !containsPositionCategory(category)) + throw new BadPositionCategoryException(); + + fPositions.remove(category); + } + + /* + * @see IDocument#removePositionUpdater + */ + public void removePositionUpdater(IPositionUpdater updater) { + for (int i= fPositionUpdaters.size() - 1; i >= 0; i--) { + if (fPositionUpdaters.get(i) == updater) { + fPositionUpdaters.remove(i); + return; + } + } + } + + /* + * @see IDocument#replace + */ + public void replace(int pos, int length, String text) throws BadLocationException { + if ((0 > pos) || (0 > length) || (pos + length > getLength())) + throw new BadLocationException(); + + DocumentEvent e= new DocumentEvent(this, pos, length, text); + fireDocumentAboutToBeChanged(e); + + getStore().replace(pos, length, text); + getTracker().replace(pos, length, text); + + fireDocumentChanged(e); + } + + /* + * @see IDocument#set + */ + public void set(String text) { + int length= getStore().getLength(); + DocumentEvent e= new DocumentEvent(this, 0, length, text); + fireDocumentAboutToBeChanged(e); + + getStore().set(text); + getTracker().set(text); + + fireDocumentChanged(e); + } + + /** + * Updates all positions of all categories to the change + * described by the document event. All registered document + * updaters are called in the sequence they have been arranged. + * Uses a robust iterator. + * + * @param event the document event describing the change to which to adapt the positions + */ + protected void updatePositions(DocumentEvent event) { + List list= new ArrayList(fPositionUpdaters); + Iterator e= list.iterator(); + while (e.hasNext()) { + IPositionUpdater u= (IPositionUpdater) e.next(); + u.update(event); + } + } + + /* + * @see IDocument#search + */ + public int search(int startPosition, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException { + + if (findString == null || findString.length() == 0) + return -1; + + ITextStore store= getStore(); + + if (startPosition < -1 || startPosition > store.getLength()) + throw new BadLocationException(); + + if (!caseSensitive) + findString= findString.toLowerCase(); + + char[] fs= new char[findString.length()]; + findString.getChars(0, fs.length, fs, 0); + + + if (forwardSearch) { + if (startPosition == -1) + startPosition= 0; + int end= getLength(); + while (startPosition < end) { + int pos= indexOf(store, fs, startPosition, caseSensitive); + if (!wholeWord || pos == -1 || isWholeWord(store, pos, pos + fs.length)) { + return pos; + } + startPosition= pos + 1; + } + } else { + if (startPosition == -1) + startPosition= getLength(); + while (startPosition >= 0) { + int pos= lastIndexOf(store, fs, startPosition, caseSensitive); + if (!wholeWord || pos == -1 || isWholeWord(store, pos, pos + fs.length)) { + return pos; + } + startPosition= pos - 1; + } + } + return -1; + } + + /** + * Returns the first index greater than <code>fromIndex</code> at which <code>str</code> + * can be found in the <code>store</code>. + * + * @param store the text store to search + * @param str the string to search + * @param fromIndex the start offset + * @param caseSensitive <code>true</code> if capitalization should be honored, <code>false</code> otherwise + * @return the offset greater than the start offset at which the search string has been found + */ + static private int indexOf(ITextStore store, char[] str, int fromIndex, boolean caseSensitive) { + int count= store.getLength(); + + if (fromIndex >= count) + return -1; + + if (fromIndex < 0) + fromIndex= 0; + + int strLen= str.length; + if (strLen == 0) // empty string always matches + return fromIndex; + + char first= str[0]; + int i= fromIndex; + int max= count - strLen; + + restart: + while (true) { + + // Look for first character + if (caseSensitive) { + while (i <= max && store.get(i) != first) + i++; + } else { + while (i <= max && Character.toLowerCase(store.get(i)) != first) + i++; + } + + if (i > max) + return -1; + + // Found first character + int j= i + 1; + int end= j + strLen - 1; + int k= 1; + if (caseSensitive) { + while (j < end) { + if (store.get(j++) != str[k++]) { + i++; + continue restart; + } + } + } else { + while (j < end) { + if (Character.toLowerCase(store.get(j++)) != str[k++]) { + i++; + continue restart; + } + } + } + + return i; // Found + } + } + + /** + * Returns the first index smaller than <code>fromIndex</code> at which <code>str</code> + * can be found in the <code>store</code>. + * + * @param store the text store to search + * @param str the string to search + * @param fromIndex the start offset + * @param caseSensitive <code>true</code> if capitalization should be honored, <code>false</code> otherwise + * @return the offset smaller than the start offset at which the search string has been found + */ + static private int lastIndexOf(ITextStore store, char[] str, int fromIndex, boolean caseSensitive) { + + if (fromIndex < 0) + return -1; + + int count= store.getLength(); + int strLen= str.length; + int rightIndex= count - strLen; + + if (fromIndex > rightIndex) + fromIndex= rightIndex; + + if (strLen == 0) // empty string always matches + return fromIndex; + + int strLastIndex= strLen - 1; + char strLastChar= str[strLastIndex]; + int min= strLen - 1; + int i= min + fromIndex; + + restart: + while (true) { + + // Look for the last character + if (caseSensitive) { + while (i >= min && store.get(i) != strLastChar) + i--; + } else { + while (i >= min && Character.toLowerCase(store.get(i)) != strLastChar) + i--; + } + + if (i < min) + return -1; + + // Found last character + int j= i - 1; + int start= j - (strLen - 1); + int k= strLastIndex - 1; + + if (caseSensitive) { + while (j > start) { + if (store.get(j--) != str[k--]) { + i--; + continue restart; + } + } + } else { + while (j > start) { + if (Character.toLowerCase(store.get(j--)) != str[k--]) { + i--; + continue restart; + } + } + } + + return start + 1; /* Found whole string. */ + } + } + + /** + * Tests if the substring is a whole word. + * + * @param store the store in which to find the string + * @param from the substring start offset + * @param to the substring endoffset + * @return <code>true</code> if the string is a whole word, otherwise <code>false</code> + */ + private static boolean isWholeWord(ITextStore store, int from, int to) { + + if (from > 0) { + char ch= store.get(from-1); + if (Character.isLetterOrDigit(ch) || ch == '_') { + return false; + } + } + if (to < store.getLength()) { + char ch= store.get(to); + if (Character.isLetterOrDigit(ch) || ch == '_' ) { + return false; + } + } + return true; + } + + + // ---------- implementation of IDocumentExtension -------------- + + /** + * Inner class to bundle a registered post notifcation replace operation together with its + * owner. + * + * @since 2.0 + */ + static private class RegisteredReplace { + /** The owner of this replace operation. */ + IDocumentListener fOwner; + /** The replace operation */ + IDocumentExtension.IReplace fReplace; + + /** + * Creates a new bundle object. + */ + RegisteredReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + fOwner= owner; + fReplace= replace; + } + }; + + /** + * Flushs all registered post notification changes. + * + * @since 2.0 + */ + private void flushPostNotificationChanges() { + if (fPostNotificationChanges != null) + fPostNotificationChanges.clear(); + } + + /** + * Executes all registered post notification changes. The process is + * repeated until no new post notification changes are added. + * + * @since 2.0 + */ + private void executePostNotificationChanges() { + + if (fStoppedCount > 0) + return; + + while (fPostNotificationChanges != null) { + List changes= fPostNotificationChanges; + fPostNotificationChanges= null; + + Iterator e= changes.iterator(); + while (e.hasNext()) { + RegisteredReplace replace = (RegisteredReplace) e.next(); + replace.fReplace.perform(this, replace.fOwner); + } + } + } + + /* + * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace) + * @since 2.0 + */ + public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + if (fPostNotificationChanges == null) + fPostNotificationChanges= new ArrayList(1); + fPostNotificationChanges.add(new RegisteredReplace(owner, replace)); + } + + /* + * @see IDocumentExtension#stopPostNotificationProcessing() + * @since 2.0 + */ + public void stopPostNotificationProcessing() { + ++ fStoppedCount; + } + + /* + * @see IDocumentExtension#resumePostNotificationProcessing() + * @since 2.0 + */ + public void resumePostNotificationProcessing() { + -- fStoppedCount; + if (fStoppedCount == 0 && fReentranceCount == 0) + executePostNotificationChanges(); + } + + /* + * @see IDocumentExtension#startSequentialRewrite(boolean) + * @since 2.0 + */ + public void startSequentialRewrite(boolean normalized) { + } + + /* + * @see IDocumentExtension#stopSequentialRewrite() + * @since 2.0 + */ + public void stopSequentialRewrite() { + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java b/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java new file mode 100644 index 000000000..f74f3051e --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/AbstractLineTracker.java @@ -0,0 +1,502 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +import java.util.ArrayList; +import java.util.List; + + + +/** + * Abstract implementation of <code>ILineTracker</code>. It lets the + * definition of line delimiters to subclasses. Assuming that '\n' is + * the only line delimiter, this abstract implementation defines the + * following line scheme: + * <ul> + * <li> "" -> [0,0] + * <li> "a" -> [0,1] + * <li> "\n" -> [0,1], [1,0] + * <li> "a\n" -> [0,2], [2,0] + * <li> "a\nb" -> [0,2], [2,1] + * <li> "a\nbc\n" -> [0,2], [2,3], [5,0] + * </ul> + * This class must be subclassed. + */ +public abstract class AbstractLineTracker implements ILineTracker { + + + /** + * Combines the information of the occurence of a line delimiter. + * <code>delimiterIndex</code> is the index where a line delimiter + * starts, whereas <code>delimiterLength</code>, indicates the length + * of the delimiter. + */ + protected static class DelimiterInfo { + public int delimiterIndex; + public int delimiterLength; + public String delimiter; + }; + + + /** The line information */ + private List fLines= new ArrayList(); + /** The length of the tracked text */ + private int fTextLength; + + + /** + * Creates a new line tracker. + */ + protected AbstractLineTracker() { + } + + /** + * Binary search for the line at a given offset. + * + * @param offset the offset whose line should be found + * @return the line of the offset + */ + private int findLine(int offset) { + + if (fLines.size() == 0) + return -1; + + int left= 0; + int right= fLines.size() -1; + int mid= 0; + Line line= null; + + while (left < right) { + + mid= (left + right) / 2; + + line= (Line) fLines.get(mid); + if (offset < line.offset) { + if (left == mid) + right= left; + else + right= mid -1; + } else if (offset > line.offset) { + if (right == mid) + left= right; + else + left= mid +1; + } else if (offset == line.offset) { + left= right= mid; + } + } + + line= (Line) fLines.get(left); + if (line.offset > offset) + -- left; + return left; + } + + /** + * Returns the number of lines covered by the specified text range. + * + * @param startLine the line where the text range starts + * @param offset the start offset of the text range + * @param length the length of the text range + * @return the number of lines covered by this text range + * @exception BadLocationException if range is undefined in this tracker + */ + private int getNumberOfLines(int startLine, int offset, int length) throws BadLocationException { + + if (length == 0) + return 1; + + int target= offset + length; + + Line l= (Line) fLines.get(startLine); + + if (l.delimiter == null) + return 1; + + if (l.offset + l.length > target) + return 1; + + if (l.offset + l.length == target) + return 2; + + return getLineNumberOfOffset(target) - startLine + 1; + } + + /* + * @see ILineTracker#getLineLength + */ + public int getLineLength(int line) throws BadLocationException { + + int lines= fLines.size(); + + if (line < 0 || line > lines) + throw new BadLocationException(); + + if (lines == 0 || lines == line) + return 0; + + Line l= (Line) fLines.get(line); + return l.length; + } + + /* + * @see ILineTracker#getLineNumberOfOffset + */ + public int getLineNumberOfOffset(int position) throws BadLocationException { + + if (position > fTextLength) + throw new BadLocationException(); + + if (position == fTextLength) { + + int lastLine= fLines.size() - 1; + if (lastLine < 0) + return 0; + + Line l= (Line) fLines.get(lastLine); + return (l.delimiter != null ? lastLine + 1 : lastLine); + } + + return findLine(position); + } + + /* + * @see ILineTracker#getLineInformationOfOffset + */ + public IRegion getLineInformationOfOffset(int position) throws BadLocationException { + if (position > fTextLength) + throw new BadLocationException(); + + if (position == fTextLength) { + int size= fLines.size(); + if (size == 0) + return new Region(0, 0); + Line l= (Line) fLines.get(size - 1); + return (l.delimiter != null ? new Line(fTextLength, 0) : new Line(fTextLength - l.length, l.length)); + } + + return getLineInformation(findLine(position)); + } + + /* + * @see ILineTracker#getLineInformation + */ + public IRegion getLineInformation(int line) throws BadLocationException { + + int lines= fLines.size(); + + if (line < 0 || line > lines) + throw new BadLocationException(); + + if (lines == 0) + return new Line(0, 0); + + if (line == lines) { + Line l= (Line) fLines.get(line - 1); + return new Line(l.offset + l.length, 0); + } + + Line l= (Line) fLines.get(line); + return (l.delimiter != null ? new Line(l.offset, l.length - l.delimiter.length()) : l); + } + + /* + * @see ILineTracker#getLineOffset + */ + public int getLineOffset(int line) throws BadLocationException { + + int lines= fLines.size(); + + if (line < 0 || line > lines) + throw new BadLocationException(); + + if (lines == 0) + return 0; + + if (line == lines) { + Line l= (Line) fLines.get(line - 1); + return l.offset + l.length; + } + + Line l= (Line) fLines.get(line); + return l.offset; + } + + /* + * @see ILineTracker#getNumberOfLines + */ + public int getNumberOfLines() { + + int lines= fLines.size(); + + if (lines == 0) + return 1; + + Line l= (Line) fLines.get(lines - 1); + return (l.delimiter != null ? lines + 1 : lines); + } + + /* + * @see ILineTracker#getNumberOfLines(int, int) + */ + public int getNumberOfLines(int position, int length) throws BadLocationException { + + if (position < 0 || position + length > fTextLength) + throw new BadLocationException(); + + if (length == 0) // optimization + return 1; + + return getNumberOfLines(getLineNumberOfOffset(position), position, length); + } + + /* + * @see ILineTracker#computeNumberOfLines(String) + */ + public int computeNumberOfLines(String text) { + int count= 0; + int start= 0; + DelimiterInfo delimiterInfo= nextDelimiterInfo(text, start); + while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) { + ++count; + start= delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength; + delimiterInfo= nextDelimiterInfo(text, start); + } + return count; + } + + + /* ----------------- manipulation ------------------------------ */ + + + /** + * Returns the info of the first delimiter found in the given + * text starting at the given offset. + * + * @param text the text to be searched + * @param offset the offset in the given text + * @return the info of the first found delimiter or <code>null</code> if + * there is no such info + */ + protected abstract DelimiterInfo nextDelimiterInfo(String text, int offset); + + + /** + * Creates the line structure for the given text. Newly created lines + * are inserted into the line structure starting at the given + * position. Returns the number of newly created lines. + * + * @param text the text for which to create a line structure + * @param insertPosition the position at which the newly created lines are inserted + * into the tracker's line structure + * @param offset the offset of all newly created lines + * @return the number of newly created lines + */ + private int createLines(String text, int insertPosition, int offset) { + + int count= 0; + int start= 0; + DelimiterInfo delimiterInfo= nextDelimiterInfo(text, 0); + + + while (delimiterInfo != null && delimiterInfo.delimiterIndex > -1) { + + int index= delimiterInfo.delimiterIndex + (delimiterInfo.delimiterLength - 1); + + if (insertPosition + count >= fLines.size()) + fLines.add(new Line(offset + start, offset + index, delimiterInfo.delimiter)); + else + fLines.add(insertPosition + count, new Line(offset + start, offset + index, delimiterInfo.delimiter)); + + ++count; + start= index + 1; + delimiterInfo= nextDelimiterInfo(text, start); + } + + if (start < text.length()) { + if (insertPosition + count < fLines.size()) { + // there is a line below the current + Line l= (Line) fLines.get(insertPosition + count); + int delta= text.length() - start; + l.offset -= delta; + l.length += delta; + } else { + fLines.add(new Line(offset + start, offset + text.length() - 1, null)); + ++count; + } + } + + return count; + } + + /** + * Keeps track of the line information when text is inserted. + * Returns the number of inserted lines. + * + * @param lineNumber the line at which the insert happens + * @param offset at which the insert happens + * @param text the inserted text + * @return the number of inserted lines + * @exception BadLocationException if offset is invalid in this tracker + */ + private int insert(int lineNumber, int offset, String text) throws BadLocationException { + + if (text == null || text.length() == 0) + return 0; + + fTextLength += text.length(); + + int size= fLines.size(); + + if (size == 0 || lineNumber >= size) + return createLines(text, size, offset); + + Line line= (Line) fLines.get(lineNumber); + DelimiterInfo delimiterInfo= nextDelimiterInfo(text, 0); + if (delimiterInfo == null || delimiterInfo.delimiterIndex == -1) { + line.length += text.length(); + return 0; + } + + + // as there is a line break, split line but do so only if rest of line is not of length 0 + int restLength= line.offset + line.length - offset; + if (restLength > 0) { + // determine start and end of the second half of the splitted line + Line lineRest= new Line(offset, restLength); + lineRest.delimiter= line.delimiter; + // shift it by the inserted text + lineRest.offset += text.length(); + // and insert in line structure + fLines.add(lineNumber + 1, lineRest); + } + + // adapt the beginning of the splitted line + line.delimiter= delimiterInfo.delimiter; + int nextStart= offset + delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength; + line.length= nextStart - line.offset; + + // insert lines for the remaining text + text= text.substring(delimiterInfo.delimiterIndex + delimiterInfo.delimiterLength); + return createLines(text, lineNumber + 1, nextStart) + 1; + } + + /** + * Keeps track of the line information when text is removed. Returns + * whether the line at which the deletion start will thereby be deleted. + * + * @param lineNumber the lineNumber at which the deletion starts + * @param offset the offset of the first deleted character + * @param length the number of deleted characters + * @return whethere the start line of the deletion has been deleted + * @exception BadLocationException if position is unkown to the tracker + */ + private boolean remove(int lineNumber, int offset, int length) throws BadLocationException { + + if (length == 0) + return false; + + int removedLineEnds= getNumberOfLines(lineNumber, offset, length) - 1; + Line line= (Line) fLines.get(lineNumber); + + if ((lineNumber == fLines.size() - 1) && removedLineEnds > 0) { + line.length -= length; + line.delimiter= null; + } else { + + ++ lineNumber; + for (int i= 1; i <= removedLineEnds; i++) { + + if (lineNumber == fLines.size()) { + line.delimiter= null; + break; + } + + Line line2= (Line) fLines.get(lineNumber); + line.length += line2.length; + line.delimiter= line2.delimiter; + fLines.remove(lineNumber); + } + line.length -= length; + } + + fTextLength -= length; + + if (line.length == 0) { + fLines.remove(line); + return true; + } + + return false; + } + + /** + * Adapts the offset of all lines with line numbers greater than the specified + * one to the given delta. + * + * @param lineNumber the line number after which to start + * @param delta the offset delta to be applied + */ + private void adaptLineOffsets(int lineNumber, int delta) { + int size= fLines.size(); + for (int i= lineNumber + 1; i < size; i++) { + Line l= (Line) fLines.get(i); + l.offset += delta; + } + } + + /* + * @see ILineTracker#replace + */ + public void replace(int position, int length, String text) throws BadLocationException { + + int lineNumber= getLineNumberOfOffset(position); + int insertLineNumber= lineNumber; + + if (remove(lineNumber, position, length)) + -- lineNumber; + + lineNumber += insert(insertLineNumber, position, text); + + int delta= -length; + if (text != null) + delta= text.length() + delta; + + if (delta != 0) + adaptLineOffsets(lineNumber, delta); + } + + /* + * @see ILineTracker#set + */ + public void set(String text) { + fLines.clear(); + if (text != null) { + fTextLength= text.length(); + createLines(text, 0, 0); + } + } + + /* + * @see ILineTracker#getLineDelimiter + */ + public String getLineDelimiter(int line) throws BadLocationException { + + int lines= fLines.size(); + + if (line < 0 || line > lines) + throw new BadLocationException(); + + if (lines == 0) + return null; + + if (line == lines) + return null; + + Line l= (Line) fLines.get(line); + return l.delimiter; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/Assert.java b/org.eclipse.text/src/org/eclipse/jface/text/Assert.java new file mode 100644 index 000000000..02cfc73a5 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/Assert.java @@ -0,0 +1,165 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +/** + * <code>Assert</code> is useful for for embedding runtime sanity checks + * in code. The static predicate methods all test a condition and throw some + * type of unchecked exception if the condition does not hold. + * <p> + * Assertion failure exceptions, like most runtime exceptions, are + * thrown when something is misbehaving. Assertion failures are invariably + * unspecified behavior; consequently, clients should never rely on + * these being thrown (or not thrown). <b>If you find yourself in the + * position where you need to catch an assertion failure, you have most + * certainly written your program incorrectly.</b> + * </p> + * <p> + * Note that an <code>assert</code> statement is slated to be added to the + * Java language in JDK 1.4, rending this class obsolete. + * </p> + */ +public final class Assert { + + /** + * <code>AssertionFailedException</code> is a runtime exception thrown + * by some of the methods in <code>Assert</code>. + * <p> + * This class is not declared public to prevent some misuses; programs that catch + * or otherwise depend on assertion failures are susceptible to unexpected + * breakage when assertions in the code are added or removed. + * </p> + */ + private static class AssertionFailedException extends RuntimeException { + + /** + * Constructs a new exception. + */ + public AssertionFailedException() { + } + + /** + * Constructs a new exception with the given message. + */ + public AssertionFailedException(String detail) { + super(detail); + } + } +/* This class is not intended to be instantiated. */ +private Assert() { +} +/** + * Asserts that an argument is legal. If the given boolean is + * not <code>true</code>, an <code>IllegalArgumentException</code> + * is thrown. + * + * @param expression the outcome of the check + * @return <code>true</code> if the check passes (does not return + * if the check fails) + * @exception IllegalArgumentException if the legality test failed + */ +public static boolean isLegal(boolean expression) { + // succeed as quickly as possible + if (expression) { + return true; + } + return isLegal(expression, "");//$NON-NLS-1$ +} +/** + * Asserts that an argument is legal. If the given boolean is + * not <code>true</code>, an <code>IllegalArgumentException</code> + * is thrown. + * The given message is included in that exception, to aid debugging. + * + * @param expression the outcome of the check + * @param message the message to include in the exception + * @return <code>true</code> if the check passes (does not return + * if the check fails) + * @exception IllegalArgumentException if the legality test failed + */ +public static boolean isLegal(boolean expression, String message) { + if (!expression) + throw new IllegalArgumentException("assertion failed; " + message); //$NON-NLS-1$ + return expression; +} +/** + * Asserts that the given object is not <code>null</code>. If this + * is not the case, some kind of unchecked exception is thrown. + * <p> + * As a general rule, parameters passed to API methods must not be + * <code>null</code> unless <b>explicitly</b> allowed in the method's + * specification. Similarly, results returned from API methods are never + * <code>null</code> unless <b>explicitly</b> allowed in the method's + * specification. Implementations are encouraged to make regular use of + * <code>Assert.isNotNull</code> to ensure that <code>null</code> + * parameters are detected as early as possible. + * </p> + * + * @param object the value to test + * @exception Throwable an unspecified unchecked exception if the object + * is <code>null</code> + */ +public static void isNotNull(Object object) { + // succeed as quickly as possible + if (object != null) { + return; + } + isNotNull(object, "");//$NON-NLS-1$ +} +/** + * Asserts that the given object is not <code>null</code>. If this + * is not the case, some kind of unchecked exception is thrown. + * The given message is included in that exception, to aid debugging. + * <p> + * As a general rule, parameters passed to API methods must not be + * <code>null</code> unless <b>explicitly</b> allowed in the method's + * specification. Similarly, results returned from API methods are never + * <code>null</code> unless <b>explicitly</b> allowed in the method's + * specification. Implementations are encouraged to make regular use of + * <code>Assert.isNotNull</code> to ensure that <code>null</code> + * parameters are detected as early as possible. + * </p> + * + * @param object the value to test + * @param message the message to include in the exception + * @exception Throwable an unspecified unchecked exception if the object + * is <code>null</code> + */ +public static void isNotNull(Object object, String message) { + if (object == null) + throw new AssertionFailedException("null argument;" + message);//$NON-NLS-1$ +} +/** + * Asserts that the given boolean is <code>true</code>. If this + * is not the case, some kind of unchecked exception is thrown. + * + * @param expression the outcome of the check + * @return <code>true</code> if the check passes (does not return + * if the check fails) + */ +public static boolean isTrue(boolean expression) { + // succeed as quickly as possible + if (expression) { + return true; + } + return isTrue(expression, "");//$NON-NLS-1$ +} +/** + * Asserts that the given boolean is <code>true</code>. If this + * is not the case, some kind of unchecked exception is thrown. + * The given message is included in that exception, to aid debugging. + * + * @param expression the outcome of the check + * @param message the message to include in the exception + * @return <code>true</code> if the check passes (does not return + * if the check fails) + */ +public static boolean isTrue(boolean expression, String message) { + if (!expression) + throw new AssertionFailedException("Assertion failed: "+message);//$NON-NLS-1$ + return expression; +} +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/BadLocationException.java b/org.eclipse.text/src/org/eclipse/jface/text/BadLocationException.java new file mode 100644 index 000000000..17f208044 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/BadLocationException.java @@ -0,0 +1,30 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Indicates the attempt to access a non-existing position. + * The attempt has been performed on a text store such as a document or string. + */ +public class BadLocationException extends Exception { + + /** + * Creates a new bad location exception. + */ + public BadLocationException() { + super(); + } + + /** + * Creates a new bad location exception. + * + * @param message the exception message + */ + public BadLocationException(String message) { + super(message); + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/BadPositionCategoryException.java b/org.eclipse.text/src/org/eclipse/jface/text/BadPositionCategoryException.java new file mode 100644 index 000000000..ba853d861 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/BadPositionCategoryException.java @@ -0,0 +1,32 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Indicates the attempt to access a non-existing position + * category in a document. + * + * @see IDocument + */ +public class BadPositionCategoryException extends Exception { + + /** + * Creates a new bad position category exception. + */ + public BadPositionCategoryException() { + super(); + } + + /** + * Creates a new bad position category exception. + * + * @param message the exception's message + */ + public BadPositionCategoryException(String message) { + super(message); + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ChildDocument.java b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocument.java new file mode 100644 index 000000000..7d2185c33 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocument.java @@ -0,0 +1,290 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + + +/** + * A child document represent a range of its parent document. + * The child document is always in sync with its parent document + * by utilizing the parent document as its <code>ITextStore</code>. + * This class is for internal use only. + * + * @see ITextStore + */ +public final class ChildDocument extends AbstractDocument { + + + /** + * Implements ITextStore based on IDocument. + */ + class TextStore implements ITextStore { + + /* + * @see ITextStore#set + */ + public void set(String txt) { + try { + fParentDocument.replace(fRange.getOffset(), fRange.getLength(), txt); + } catch (BadLocationException x) { + // cannot happen + } + } + + /* + * @see ITextStore#replace + */ + public void replace(int offset, int length, String txt) { + try { + fParentDocument.replace(fRange.getOffset() + offset, length, txt); + } catch (BadLocationException x) { + // ignored as surrounding document should have handled this + } + } + + /* + * @see ITextStore#getLength + */ + public int getLength() { + return fRange.getLength(); + } + + /* + * @see ITextStore#get + */ + public String get(int offset, int length) { + try { + return fParentDocument.get(fRange.getOffset() + offset, length); + } catch (BadLocationException x) { + } + + return null; + } + + /* + * @see ITextStore#get + */ + public char get(int offset) { + try { + return fParentDocument.getChar(fRange.getOffset() + offset); + } catch (BadLocationException x) { + } + + return (char) 0; + } + }; + + + + /** The parent document */ + private IDocument fParentDocument; + /** + * The parent document as document extension + * @since 2.0 + */ + private IDocumentExtension fExtension; + + /** The section inside the parent document */ + private Position fRange; + /** The document event issued by the parent document */ + private DocumentEvent fParentEvent; + /** The document event issued and to be issued by the child document */ + private DocumentEvent fEvent; + /** Indicates whether the child document initiated a parent document update or not */ + private boolean fIsUpdating= false; + + /** + * Creates a child document for the given range of the given parent document. + * + * @param parentDocument the parent Document + * @param range the parent document range covered by the child document + */ + public ChildDocument(IDocument parentDocument, Position range) { + super(); + + fParentDocument= parentDocument; + if (fParentDocument instanceof IDocumentExtension) + fExtension= (IDocumentExtension) fParentDocument; + + fRange= range; + + ITextStore s= new TextStore(); + ILineTracker tracker= new DefaultLineTracker(); + tracker.set(s.get(0, fRange.getLength())); + + setTextStore(s); + setLineTracker(tracker); + + completeInitialization(); + } + + /** + * Sets the child document's parent document range. + * + * @param offset the offset of the parent document range + * @param length the length of the parent document range + */ + public void setParentDocumentRange(int offset, int length) throws BadLocationException { + + if (offset < 0 || length < 0 || offset + length > fParentDocument.getLength()) + throw new BadLocationException(); + + fRange.setOffset(offset); + fRange.setLength(length); + + getTracker().set(fParentDocument.get(offset, length)); + } + + /** + * Returns parent document + * + * @return the parent document + */ + public IDocument getParentDocument() { + return fParentDocument; + } + + /** + * Returns the range of the parent document covered by this child document. + * + * @return the child document's parent document range + */ + public Position getParentDocumentRange() { + return fRange; + } + + /** + * Transforms a document event of the parent document into a child document + * based document event. + * + * @param e the parent document event + * @return the child document event + */ + private DocumentEvent normalize(DocumentEvent e) { + + int delta= e.getOffset() - fRange.getOffset(); + int offset= delta < 0 ? 0 : delta; + int length= delta < 0 ? e.fLength + delta : e.fLength; + if (offset + length > fRange.getLength()) + length= fRange.getLength() - offset; + + return new ChildDocumentEvent(this, offset, length, e.fText, e); + } + + /** + * When called this child document is informed about a forthcoming change + * of its parent document. This child document checks whether the parent + * document changed affects it and if so informs all document listeners. + * + * @param event the parent document event + */ + public void parentDocumentAboutToBeChanged(DocumentEvent event) { + + fParentEvent= event; + + if (fRange.overlapsWith(event.fOffset, event.fLength)) { + fEvent= normalize(event); + delayedFireDocumentAboutToBeChanged(); + } else + fEvent= null; + } + + /** + * When called this child document is informed about a change of its parent document. + * If this child document is affected it informs all of its document listeners. + * + * @param event the parent document event + */ + public void parentDocumentChanged(DocumentEvent event) { + if ( !fIsUpdating && event == fParentEvent && fEvent != null) { + try { + getTracker().replace(fEvent.fOffset, fEvent.fLength, fEvent.fText); + fireDocumentChanged(fEvent); + } catch (BadLocationException x) { + Assert.isLegal(false); + } + } + } + + /* + * @see AbstractDocument#fireDocumentAboutToBeChanged + */ + protected void fireDocumentAboutToBeChanged(DocumentEvent event) { + // delay it until there is a notification from the parent document + // otherwise there it is expensive to construct the parent document information + } + + /** + * Fires the child document event as about-to-be-changed event to all + * registed listeners. + */ + private void delayedFireDocumentAboutToBeChanged() { + super.fireDocumentAboutToBeChanged(fEvent); + } + + /** + * Ignores the given event and sends the similar child document event instead. + * + * @param event the event to be ignored + */ + protected void fireDocumentChanged(DocumentEvent event) { + super.fireDocumentChanged(fEvent); + } + + /* + * @see IDocument#replace(int, int, String) + * @since 2.0 + */ + public void replace(int offset, int length, String text) throws BadLocationException { + try { + fIsUpdating= true; + if (fExtension != null) + fExtension.stopPostNotificationProcessing(); + + super.replace(offset, length, text); + + } finally { + fIsUpdating= false; + if (fExtension != null) + fExtension.resumePostNotificationProcessing(); + } + } + + /* + * @see IDocument#set(String) + * @since 2.0 + */ + public void set(String text) { + try { + fIsUpdating= true; + if (fExtension != null) + fExtension.stopPostNotificationProcessing(); + + super.set(text); + + } finally { + fIsUpdating= false; + if (fExtension != null) + fExtension.resumePostNotificationProcessing(); + } + } + + /* + * @see IDocumentExtension#registerPostNotificationReplace(IDocumentListener, IDocumentExtension.IReplace) + * @since 2.0 + */ + public void registerPostNotificationReplace(IDocumentListener owner, IDocumentExtension.IReplace replace) { + if (!fIsUpdating) + throw new UnsupportedOperationException(); + super.registerPostNotificationReplace(owner, replace); + } +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentEvent.java b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentEvent.java new file mode 100644 index 000000000..cc5e52976 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentEvent.java @@ -0,0 +1,39 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +/** + * A child document event represents a parent document event as a + * child-relative document event. It also carries the original event. + */ +public class ChildDocumentEvent extends DocumentEvent { + + /** The parent document event */ + private DocumentEvent fParentEvent; + + /** + * Creates a new child document event. + * + * @param doc the child document + * @param offset the offset in the child document + * @param length the length in the child document + * @param text the substitution text + * @param parentEvent the parent Event + */ + public ChildDocumentEvent(IDocument doc, int offset, int length, String text, DocumentEvent parentEvent) { + super(doc, offset, length, text); + fParentEvent= parentEvent; + } + + /** + * Returns this event's parent event. + * + * @return this event's parent event + */ + public DocumentEvent getParentEvent() { + return fParentEvent; + } +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentManager.java b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentManager.java new file mode 100644 index 000000000..2b9ed9a4b --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ChildDocumentManager.java @@ -0,0 +1,312 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + + +/** + * Manages a set of child documents for given parent documents. + * A child document represents a particular range of the parent + * document and is accordingly adapted to changes of the parent document. + * Vice versa, the parent document is accordingly adapted to changes of + * its child documents. The manager does not maintain any particular management + * structure but utilizes mechanisms given by <code>IDocument</code> such + * as position categories and position updaters. <p> + * + * For internal use only. + */ +public final class ChildDocumentManager implements IDocumentListener { + + + /** + * Name of the position categories used to keep track of the child + * documents offset ranges into the parent document. + */ + public final static String CHILDDOCUMENTS= "__childdocuments"; //$NON-NLS-1$ + + + /** + * Positions which are used to mark the child documents offset ranges into + * the parent documents. This position uses as bidirectional reference as + * it knows the child document as well as the parent document. + */ + static class ChildPosition extends Position { + + public IDocument fParentDocument; + public ChildDocument fChildDocument; + + public ChildPosition(IDocument parentDocument, int offset, int length) { + super(offset, length); + fParentDocument= parentDocument; + } + + /** + * Changed to be compatible to the position updater behavior + * @see Position#overlapsWith(int, int) + */ + public boolean overlapsWith(int offset, int length) { + boolean append= (offset == this.offset + this.length) && length == 0; + return append || super.overlapsWith(offset, length); + } + }; + + + /** + * The position updater used to adapt the positions representing + * the child document ranges to changes of the parent document. + */ + static class ChildPositionUpdater extends DefaultPositionUpdater { + + /** + * Creates the position updated. + */ + protected ChildPositionUpdater() { + super(CHILDDOCUMENTS); + } + + /** + * Child document ranges cannot be deleted other then by calling + * freeChildDocument. + */ + protected boolean notDeleted() { + return true; + } + + /** + * If an insertion happens at a child document's start offset, the + * position is extended rather than shifted. Also, if something is added + * right behind the end of the position, the position is extended rather + * than kept stable. + */ + protected void adaptToInsert() { + + int myStart= fPosition.offset; + int myEnd= fPosition.offset + fPosition.length; + myEnd= Math.max(myStart, myEnd); + + int yoursStart= fOffset; + int yoursEnd= fOffset + fReplaceLength -1; + yoursEnd= Math.max(yoursStart, yoursEnd); + + if (myEnd < yoursStart) + return; + + if (myStart <= yoursStart) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + } + }; + + /** + * The child document partitioner uses the parent document to answer all questions. + */ + static class ChildPartitioner implements IDocumentPartitioner { + + protected ChildDocument fChildDocument; + protected IDocument fParentDocument; + + protected ChildPartitioner() { + } + + /* + * @see IDocumentPartitioner#getPartition(int) + */ + public ITypedRegion getPartition(int offset) { + try { + offset += fChildDocument.getParentDocumentRange().getOffset(); + return fParentDocument.getPartition(offset); + } catch (BadLocationException x) { + } + + return null; + } + + /* + * @see IDocumentPartitioner#computePartitioning(int, int) + */ + public ITypedRegion[] computePartitioning(int offset, int length) { + try { + offset += fChildDocument.getParentDocumentRange().getOffset(); + return fParentDocument.computePartitioning(offset, length); + } catch (BadLocationException x) { + } + + return null; + } + + /* + * @see IDocumentPartitioner#getContentType(int) + */ + public String getContentType(int offset) { + try { + offset += fChildDocument.getParentDocumentRange().getOffset(); + return fParentDocument.getContentType(offset); + } catch (BadLocationException x) { + } + + return null; + } + + /* + * @see IDocumentPartitioner#getLegalContentTypes() + */ + public String[] getLegalContentTypes() { + return fParentDocument.getLegalContentTypes(); + } + + /* + * @see IDocumentPartitioner#documentChanged(DocumentEvent) + */ + public boolean documentChanged(DocumentEvent event) { + // ignore as the parent does this for us + return false; + } + + /* + * @see IDocumentPartitioner#documentAboutToBeChanged(DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent event) { + // ignore as the parent does this for us + } + + /* + * @see IDocumentPartitioner#disconnect() + */ + public void disconnect() { + fChildDocument= null; + fParentDocument= null; + } + + /* + * @see IDocumentPartitioner#connect(IDocument) + */ + public void connect(IDocument childDocument) { + Assert.isTrue(childDocument instanceof ChildDocument); + fChildDocument= (ChildDocument) childDocument; + fParentDocument= fChildDocument.getParentDocument(); + } + }; + + + + /** The position updater shared by all documents which have child documents */ + private IPositionUpdater fChildPositionUpdater; + + + + /** + * Returns the child position updater. If necessary, it is dynamically created. + * + * @return the child position updater + */ + protected IPositionUpdater getChildPositionUpdater() { + if (fChildPositionUpdater == null) + fChildPositionUpdater= new ChildPositionUpdater(); + return fChildPositionUpdater; + } + + /** + * Creates and returns a new child document for the specified range of the given parent document. + * The created child document is initialized with a child document partitioner. + * + * @param parent the parent document + * @param offset the offset of the parent document range + * @param length the length of the parent document range + * @exception BadLocationException if the specified range is invalid in the parent document + */ + public ChildDocument createChildDocument(IDocument parent, int offset, int length) throws BadLocationException { + + if (!parent.containsPositionCategory(CHILDDOCUMENTS)) { + parent.addPositionCategory(CHILDDOCUMENTS); + parent.addPositionUpdater(getChildPositionUpdater()); + parent.addDocumentListener(this); + } + + ChildPosition pos= new ChildPosition(parent, offset, length); + try { + parent.addPosition(CHILDDOCUMENTS, pos); + } catch (BadPositionCategoryException x) { + // cannot happen + } + + ChildDocument child= new ChildDocument(parent, pos); + IDocumentPartitioner partitioner= new ChildPartitioner(); + child.setDocumentPartitioner(partitioner); + partitioner.connect(child); + + pos.fChildDocument= child; + + return child; + } + + /** + * Disconnects the given child document from it's parent document and frees + * all resources which are no longer needed. + * + * @param childDocument the child document to be freed + */ + public void freeChildDocument(ChildDocument childDocument) { + + childDocument.getDocumentPartitioner().disconnect(); + + ChildPosition pos= (ChildPosition) childDocument.getParentDocumentRange(); + IDocument parent= pos.fParentDocument; + + try { + parent.removePosition(CHILDDOCUMENTS, pos); + Position[] category= parent.getPositions(CHILDDOCUMENTS); + if (category.length == 0) { + parent.removeDocumentListener(this); + parent.removePositionUpdater(getChildPositionUpdater()); + parent.removePositionCategory(CHILDDOCUMENTS); + } + } catch (BadPositionCategoryException x) { + // cannot happen + } + } + + /** + * Informs all child documents of the document which issued this document event. + * + * @param about indicates whether the change is about to happen or alread happend + * @param event the document event which will be processed to inform child documents + */ + protected void fireDocumentEvent(boolean about, DocumentEvent event) { + try { + + IDocument parent= event.getDocument(); + Position[] children= parent.getPositions(CHILDDOCUMENTS); + for (int i= 0; i < children.length; i++) { + Object o= children[i]; + if (o instanceof ChildPosition) { + ChildPosition pos= (ChildPosition) o; + if (about) + pos.fChildDocument.parentDocumentAboutToBeChanged(event); + else + pos.fChildDocument.parentDocumentChanged(event); + } + } + } catch (BadPositionCategoryException x) { + // cannot happen + } + } + + /* + * @see IDocumentListener#documentChanged(DocumentEvent) + */ + public void documentChanged(DocumentEvent event) { + fireDocumentEvent(false, event); + } + + /* + * @see IDocumentListener#documentAboutToBeChanged(DocumentEvent) + */ + public void documentAboutToBeChanged(DocumentEvent event) { + fireDocumentEvent(true, event); + } +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ConfigurableLineTracker.java b/org.eclipse.text/src/org/eclipse/jface/text/ConfigurableLineTracker.java new file mode 100644 index 000000000..817bd9cdf --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ConfigurableLineTracker.java @@ -0,0 +1,58 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + +/** + * Standard implementation of a generic <code>ILineTracker</code>. + * The line tracker can be configured with the set of legal line delimiters. + * Line delimiters are unconstrainted. The line delimiters are used to + * compute the tracker's line structure. In the case of overlapping line delimiters, + * the longest line delimiter is given precedence of the shorter ones.<p> + * This class is not intended to be subclassed. + */ +public class ConfigurableLineTracker extends AbstractLineTracker { + + + /** The strings which are considered being the line delimiter */ + private String[] fDelimiters; + /** A predefined delimiter info which is always reused as return value */ + private DelimiterInfo fDelimiterInfo= new DelimiterInfo(); + + + /** + * Creates a standard line tracker for the given line delimiters. + * + * @param legalLineDelimiters the tracker's legal line delimiters, + * may not be <code>null</code> and must be longer than 0 + */ + public ConfigurableLineTracker(String[] legalLineDelimiters) { + Assert.isTrue(legalLineDelimiters != null && legalLineDelimiters.length > 0); + fDelimiters= legalLineDelimiters; + } + + /* + * @see ILineDelimiter@getLegalLineDelimiters + */ + public String[] getLegalLineDelimiters() { + return fDelimiters; + } + + /* + * @see AbstractLineTracker#nextDelimiterInfo(String, int) + */ + protected DelimiterInfo nextDelimiterInfo(String text, int offset) { + int[] info= TextUtilities.indexOf(fDelimiters, text, offset); + if (info[0] == -1) + return null; + + fDelimiterInfo.delimiterIndex= info[0]; + fDelimiterInfo.delimiter= fDelimiters[info[1]]; + fDelimiterInfo.delimiterLength= fDelimiterInfo.delimiter.length(); + return fDelimiterInfo; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/DefaultLineTracker.java b/org.eclipse.text/src/org/eclipse/jface/text/DefaultLineTracker.java new file mode 100644 index 000000000..4f4b0eca9 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/DefaultLineTracker.java @@ -0,0 +1,74 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + +/** + * Standard implementation of <code>ILineTracker</code>. + * The line tracker considers the three common line + * delimiters which are '\n', '\r', '\r\n'.<p> + * This class is not intended to be subclassed. + */ +public class DefaultLineTracker extends AbstractLineTracker { + + /** The predefined delimiters of this tracker */ + public final static String[] DELIMITERS= { "\r", "\n", "\r\n" }; //$NON-NLS-3$ //$NON-NLS-1$ //$NON-NLS-2$ + /** A predefined delimiter info which is always reused as return value */ + private DelimiterInfo fDelimiterInfo= new DelimiterInfo(); + + + /** + * Creates a standard line tracker. + */ + public DefaultLineTracker() { + } + + /* + * @see ILineDelimiter@getLegalLineDelimiters + */ + public String[] getLegalLineDelimiters() { + return DELIMITERS; + } + + /* + * @see AbstractLineTracker#nextDelimiterInfo(String, int) + */ + protected DelimiterInfo nextDelimiterInfo(String text, int offset) { + + char ch; + int length= text.length(); + for (int i= offset; i < length; i++) { + + ch= text.charAt(i); + if (ch == '\r') { + + if (i + 1 < length) { + if (text.charAt(i + 1) == '\n') { + fDelimiterInfo.delimiter= DELIMITERS[2]; + fDelimiterInfo.delimiterIndex= i; + fDelimiterInfo.delimiterLength= 2; + return fDelimiterInfo; + } + } + + fDelimiterInfo.delimiter= DELIMITERS[0]; + fDelimiterInfo.delimiterIndex= i; + fDelimiterInfo.delimiterLength= 1; + return fDelimiterInfo; + + } else if (ch == '\n') { + + fDelimiterInfo.delimiter= DELIMITERS[1]; + fDelimiterInfo.delimiterIndex= i; + fDelimiterInfo.delimiterLength= 1; + return fDelimiterInfo; + } + } + + return null; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/DefaultPositionUpdater.java b/org.eclipse.text/src/org/eclipse/jface/text/DefaultPositionUpdater.java new file mode 100644 index 000000000..c5044eb3f --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/DefaultPositionUpdater.java @@ -0,0 +1,219 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Default implementation of <code>IPositionUpdater</code>. + * A default position updater must be configured with the position category + * whose positions it will update. Other position categories are not affected + * by this updater.<p> + * This implementation follows the following specification: + * <ul> + * <li> Inserting or deleting text before the position shifts the position accordingly. + * <li> Inserting text at the position offset shifts the position accordingly. + * <li> Inserting or deleting text completely surrounded by the position shrinks or stretches the position. + * <li> Inserting or deleting text after a position does not affect the position. + * <li> Deleting text which completly contains the position deletes the position. + * <li> Replacing text overlapping with the position considered as a sequence of first + * deleting the replaced text and afterwards inserting the new text. Thus, a + * position might first be shifted and shrink and then be stretched. + * </ul> + * This class can be used as is or be adapted by subclasses. Fields are protected to + * allow subclasses direct access. Because of the frequency with which position updaters + * are used this is a performance decision. + */ +public class DefaultPositionUpdater implements IPositionUpdater { + + /** The position category the updater draws responsible for */ + private String fCategory; + + /** Caches the currently investigated position */ + protected Position fPosition; + /** Remembers the original state of the investigated position */ + protected Position fOriginalPosition= new Position(0, 0); + /** Caches the offset of the replaced text */ + protected int fOffset; + /** Caches the length of the replaced text */ + protected int fLength; + /** Caches the length of the newly inserted text */ + protected int fReplaceLength; + /** Catches the document */ + protected IDocument fDocument; + + + /** + * Creates a new default positon updater for the given category. + * + * @param category the category the updater is responsible for + */ + public DefaultPositionUpdater(String category) { + fCategory= category; + } + + /** + * Returns the category this updater is resonsible for. + * + * @return the category this updater is resonsible for + */ + protected String getCategory() { + return fCategory; + } + + /** + * Adapts the currently investigated position to an insertion. + */ + protected void adaptToInsert() { + + int myStart= fPosition.offset; + int myEnd= fPosition.offset + fPosition.length -1; + myEnd= Math.max(myStart, myEnd); + + int yoursStart= fOffset; + int yoursEnd= fOffset + fReplaceLength -1; + yoursEnd= Math.max(yoursStart, yoursEnd); + + if (myEnd < yoursStart) + return; + + if (fLength <= 0) { + + if (myStart < yoursStart) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + + } else { + + if (myStart <= yoursStart && fOriginalPosition.offset <= yoursStart) + fPosition.length += fReplaceLength; + else + fPosition.offset += fReplaceLength; + } + } + + /** + * Adapts the currently investigated position to a deletion. + */ + protected void adaptToRemove() { + + int myStart= fPosition.offset; + int myEnd= fPosition.offset + fPosition.length -1; + myEnd= Math.max(myStart, myEnd); + + int yoursStart= fOffset; + int yoursEnd= fOffset + fLength -1; + yoursEnd= Math.max(yoursStart, yoursEnd); + + if (myEnd < yoursStart) + return; + + if (myStart <= yoursStart) { + + if (yoursEnd <= myEnd) + fPosition.length -= fLength; + else + fPosition.length -= (myEnd - yoursStart +1); + + } else if (yoursStart < myStart) { + + if (yoursEnd < myStart) + fPosition.offset -= fLength; + else { + fPosition.offset -= (myStart - yoursStart); + fPosition.length -= (yoursEnd - myStart +1); + } + + } + + // validate position to allowed values + if (fPosition.offset < 0) + fPosition.offset= 0; + + if (fPosition.length < 0) + fPosition.length= 0; + } + + /** + * Adapts the currently investigated position to the replace operation. + * First it checks whether the change replaces the whole range of the position. + * If not, it performs first the deletion of the previous text and afterwards + * the insertion of the new text. + */ + protected void adaptToReplace() { + + if (fPosition.offset == fOffset && fPosition.length == fLength && fPosition.length > 0) { + + // replace the whole range of the position + fPosition.length += (fReplaceLength - fLength); + if (fPosition.length < 0) { + fPosition.offset += fPosition.length; + fPosition.length= 0; + } + + } else { + + if (fLength > 0) + adaptToRemove(); + + if (fReplaceLength > 0) + adaptToInsert(); + } + } + + /** + * Determines whether the currently investigated position has been deleted by + * the replace operation specified in the current event. If so, it deletes + * the position and removes it from the document's position category. + * + * @return <code>true</code> if position has been deleted + */ + protected boolean notDeleted() { + + if (fOffset < fPosition.offset && (fPosition.offset + fPosition.length < fOffset + fLength)) { + + fPosition.delete(); + + try { + fDocument.removePosition(fCategory, fPosition); + } catch (BadPositionCategoryException x) { + } + + return false; + } + + return true; + } + + /* + * @see IPositionUpdater#update(DocumentEvent event) + */ + public void update(DocumentEvent event) { + + try { + + Position[] category= event.getDocument().getPositions(fCategory); + + fOffset= event.getOffset(); + fLength= event.getLength(); + fReplaceLength= (event.getText() == null ? 0 : event.getText().length()); + fDocument= event.getDocument(); + + for (int i= 0; i < category.length; i++) { + + fPosition= category[i]; + fOriginalPosition.offset= fPosition.offset; + fOriginalPosition.length= fPosition.length; + + if (notDeleted()) + adaptToReplace(); + } + + } catch (BadPositionCategoryException x) { + // do nothing + } + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/Document.java b/org.eclipse.text/src/org/eclipse/jface/text/Document.java new file mode 100644 index 000000000..688e32832 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/Document.java @@ -0,0 +1,71 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Default document implementation. Uses a gap text store as text store and + * installs a line tracker considering the following strings as line delimiters + * "\n", "\r", "\r\n". The document is ready to use. It has a default position + * category for which a default position updater is installed. + * + * @see GapTextStore + */ +public class Document extends AbstractDocument { + + + /** + * Creates a new empty document. + */ + public Document() { + super(); + setTextStore(new GapTextStore(50, 300)); + setLineTracker(new DefaultLineTracker()); + completeInitialization(); + } + + /** + * Creates a new document with the given initial content. + * + * @param initialContent the document's initial content + */ + public Document(String initialContent) { + super(); + setTextStore(new GapTextStore(50, 300)); + setLineTracker(new DefaultLineTracker()); + getStore().set(initialContent); + getTracker().set(initialContent); + completeInitialization(); + } + + /* + * @see IDocumentExtension#startSequentialRewrite(boolean) + * @since 2.0 + */ + public void startSequentialRewrite(boolean normalized) { + ITextStore store= new SequentialRewriteTextStore(getStore()); + setTextStore(store); + } + + /* + * @see IDocumentExtension#stopSequentialRewrite() + * @since 2.0 + */ + public void stopSequentialRewrite() { + if (getStore() instanceof SequentialRewriteTextStore) { + SequentialRewriteTextStore srws= (SequentialRewriteTextStore) getStore(); + ITextStore source= srws.getSourceStore(); + setTextStore(source); + srws.dispose(); + } + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/DocumentEvent.java b/org.eclipse.text/src/org/eclipse/jface/text/DocumentEvent.java new file mode 100644 index 000000000..ea9aa5518 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/DocumentEvent.java @@ -0,0 +1,84 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + +/** + * Specification of changes applied to documents. + * All changes are represented as replace commands, i.e. + * specifying a document range whose text gets replaced with different text. + * In addition to this information, the event also contains the changed document. + * + * @see IDocument + */ +public class DocumentEvent { + + /** The changed document */ + public IDocument fDocument; + /** The document offset */ + public int fOffset; + /** Length of the replaced document text */ + public int fLength; + /** Text inserted into the document */ + public String fText; + + /** + * Creates a new document event. + * + * @param doc the changed document + * @param offset the offset of the replaced text + * @param length the length of the replaced text + * @param text the substitution text + */ + public DocumentEvent(IDocument doc, int offset, int length, String text) { + + Assert.isNotNull(doc); + Assert.isTrue(offset >= 0); + Assert.isTrue(length >= 0); + + fDocument= doc; + fOffset= offset; + fLength= length; + fText= text; + } + + /** + * Returns the changed document. + * + * @return the changed document + */ + public IDocument getDocument() { + return fDocument; + } + + /** + * Returns the offset of the change + * + * @return the offset of the change + */ + public int getOffset() { + return fOffset; + } + + /** + * Returns the length of the replaced text. + * + * @return the length of the replaced text + */ + public int getLength() { + return fLength; + } + + /** + * Returns the text that has been inserted. + * + * @return the text that has been inserted + */ + public String getText() { + return fText; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/GapTextStore.java b/org.eclipse.text/src/org/eclipse/jface/text/GapTextStore.java new file mode 100644 index 000000000..6a8d8827c --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/GapTextStore.java @@ -0,0 +1,253 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + + +/** + * Implements a gap managing text store. The gap text store + * relies on the assumption that subsequent changes of a document are co-located. + * The start of the gap is always moved to the location of the last change. The + * size of the gap varies between the low water mark and the high water mark. <p> + * This class is not intended to be subclassed. + */ +public class GapTextStore implements ITextStore { + + /** The store's content */ + private char[] fContent= new char[0]; + /** Starting index of the gap */ + private int fGapStart= -1; + /** End index of the gap */ + private int fGapEnd= -1; + + /** The high water mark. If the gap is larger than this, it will be shrunken */ + private int fHighWatermark; + /** The low water mark, If this gap is smaller than this, it will be extended */ + private int fLowWatermark; + + /** + * Creates a new empty text store using the specified low and high watermarks. + * + * @param lowWatermark if this gap is ever smaller than this, it will automatically be extended + * @param highWatermark if the gap is ever larger than this, it will automatically be shrunken + */ + public GapTextStore(int lowWatermark, int highWatermark) { + Assert.isTrue(lowWatermark < highWatermark); + fLowWatermark= lowWatermark; + fHighWatermark= highWatermark; + } + + /** + * Adjusts the gap so that is at the right offset and capable of handling + * the addition of a specified number of characters without having to be shifted. + * The <code>sizeHint</code> represents the range that will be filled afterwards. + * If the gap is already at the right offset, it must only be + * resized if it will be no longer between the low and high watermark. However, + * on delete (sizeHint < 0) at the edges of the gap, the gap is only enlarged. + * + * @param offset the offset at which the change happens + * @param sizeHint the number of character which will be inserted + */ + private void adjustGap(int offset, int sizeHint) { + + if (offset == fGapStart) { + int size= (fGapEnd - fGapStart) - sizeHint; + if (fLowWatermark <= size && size <= fHighWatermark) + return; + } + + moveAndResizeGap(offset, sizeHint); + } + + /** + * Moves the gap to the specified offset and adjust its size to the + * anticipated change size. The given size represents the expected + * range of the gap that will be filled after the gap has been moved. + * Thus the gap is resized to actual size + the specified size and + * moved to the given offset. + * + * @param offset the offset where the gap is moved to + * @param size the anticipated size of the change + */ + private void moveAndResizeGap(int offset, int size) { + + char[] content= null; + int oldSize= fGapEnd - fGapStart; + int newSize= fHighWatermark + size; + + + if (newSize < 0) { + + if (oldSize > 0) { + content= new char[fContent.length - oldSize]; + System.arraycopy(fContent, 0, content, 0, fGapStart); + System.arraycopy(fContent, fGapEnd, content, fGapStart, content.length - fGapStart); + fContent= content; + } + fGapStart= fGapEnd= offset; + return; + } + + + content= new char[fContent.length + (newSize - oldSize)]; + + int newGapStart= offset; + int newGapEnd= newGapStart + newSize; + + if (oldSize == 0) { + + System.arraycopy(fContent, 0, content, 0, newGapStart); + System.arraycopy(fContent, newGapStart, content, newGapEnd, content.length - newGapEnd); + + } else if (newGapStart < fGapStart) { + + int delta= fGapStart - newGapStart; + System.arraycopy(fContent, 0, content, 0, newGapStart); + System.arraycopy(fContent, newGapStart, content, newGapEnd, delta); + System.arraycopy(fContent, fGapEnd, content, newGapEnd + delta, fContent.length - fGapEnd); + + } else { + + int delta= newGapStart - fGapStart; + System.arraycopy(fContent, 0, content, 0, fGapStart); + System.arraycopy(fContent, fGapEnd, content, fGapStart, delta); + System.arraycopy(fContent, fGapEnd + delta, content, newGapEnd, content.length - newGapEnd); + } + + + fContent= content; + fGapStart= newGapStart; + fGapEnd= newGapEnd; + } + + /* + * @see ITextStore#get + */ + public char get(int offset) { + + if (offset < fGapStart) + return fContent[offset]; + + int gapLength= fGapEnd - fGapStart; + return fContent[offset + gapLength]; + } + + /* + * @see ITextStore#get + */ + public String get(int offset, int length) { + + int end= offset + length; + + if (fContent == null) + return ""; //$NON-NLS-1$ + + if (end <= fGapStart) + return new String(fContent, offset, length); + + if (fGapStart < offset) { + int gapLength= fGapEnd - fGapStart; + return new String(fContent, offset + gapLength , length); + } + + StringBuffer buf= new StringBuffer(); + buf.append(fContent, offset, fGapStart - offset); + buf.append(fContent, fGapEnd, end - fGapStart); + return buf.toString(); + } + + /* + * @see ITextStore#getLength + */ + public int getLength() { + int length= fGapEnd - fGapStart; + return (fContent.length - length); + } + + + /* + * @see ITextStore#replace + */ + public void replace(int offset, int length, String text) { + + int textLength= (text == null ? 0 : text.length()); + + // handle delete at the edges of the gap + if (textLength == 0) { + if (offset <= fGapStart && offset + length >= fGapStart && fGapStart > -1 && fGapEnd > -1) { + length -= fGapStart - offset; + fGapStart= offset; + fGapEnd += length; + return; + } + } + + // move gap + adjustGap(offset + length, textLength - length); + + // overwrite + int min= Math.min(textLength, length); + for (int i= offset, j= 0; i < offset + min; i++, j++) + fContent[i]= text.charAt(j); + + if (length > textLength) { + // enlarge the gap + fGapStart -= (length - textLength); + } else if (textLength > length) { + // shrink gap + fGapStart += (textLength - length); + for (int i= length; i < textLength; i++) + fContent[offset + i]= text.charAt(i); + } + } + + /** + * Sets the content to <code>text</code> and removes the gap + * since there are no sensible predictions about + * where the next change will occur. + * @see ITextStore#set + */ + public void set(String text) { + + if (text == null) + text= ""; //$NON-NLS-1$ + + fContent= text.toCharArray(); + + fGapStart= -1; + fGapEnd= -1; + } + + /** + * Returns a copy of the content of this text store. + * For internal use only. + * + * @return a copy of the content of this text store + */ + protected String getContentAsString() { + return new String(fContent); + } + + /** + * Returns the start index of the gap managed by this text store. + * For internal use only. + * + * @return the start index of the gap managed by this text store + */ + protected int getGapStartIndex() { + return fGapStart; + } + + /** + * Returns the end index of the gap managed by this text store. + * For internal use only. + * + * @return the end index of the gap managed by this text store + */ + protected int getGapEndIndex() { + return fGapEnd; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocument.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocument.java new file mode 100644 index 000000000..4c5beaea1 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocument.java @@ -0,0 +1,567 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import java.util.List; + + +/** + * An <code>IDocument</code> represents text providing support for + * <ul> + * <li> text manipulation + * <li> positions + * <li> partitions + * <li> line information + * <li> search + * <li> document change listeners + * <li> document partition change listeners + * </ul> + * + * A document allows to set its content and to manipulate it. For manipulation + * a document provides the <code>replace</code> method which substitutes a given + * string for a specified text range in the document. On each document change, all + * registered document listeners are informed exactly once. + * + * Positions are stickers to the document's text that are updated when the + * document is changed. Positions are updated by <code>IPositionUpdater</code>s. Position + * updaters are managed as a list. The list defines the sequence in which position + * updaters are invoked.This way, position updaters may rely on each other. + * Positions are grouped into categories. A category is a ordered list of positions. + * the document defines the order of position in a category based on the position's offset + * based on the implementation of the method <code>computeIndexInCategory</code>. + * Each document must support a default position category whose name is specified by this + * interface.<p> + * + * A document can be considered consisting of a sequence of not overlapping partitions. + * A partition is defined by its offset, its length, and its type. Partitions are + * updated on every document manipulation and ensured to be up-to-date when the document + * listeners are informed. A document uses an <code>IDocumentPartitioner</code> to + * manage its partitions. A document may be unpartitioned which happens when there is no + * partitioner. In this case, the document is considered as one singloe partition of a + * default type. The default type is specified by this interface. If a document change + * changes the document's partitioning all registered partitioning listeners are + * informed exactly once.<p> + * + * An <code>IDocument</code> uses an <code>ILineTracker</code> to map line numbers and character + * positions onto each other based on the document's line delimiters. When moving text + * between documents using different line delimiters, the text must be converted to + * use the target document's line delimiters. <p> + * + * <code>IDocument</code> throws <code>BadLocationException</code> if the parameters of + * queries or manipulation requests are not inside the bounds of the document. The purpose + * of this style of exception handling is + * <ul> + * <li> prepare document for multi-thread access + * <li> allow clients to implement backtracking recovery methods + * <li> prevent clients from upfront contract checking when dealing with documents. + * </ul> + * Clients may implement this interface or use the default implementation provided + * by <code>AbstractDocument</code> and <code>Document</code>. + * + * @see Position + * @see IPositionUpdater + * @see IDocumentPartitioner + * @see ILineTracker + * @see IDocumentListener + * @see IDocumentPartitioningListener + */ +public interface IDocument { + + + /** + * The identifier of the default position category. + */ + final static String DEFAULT_CATEGORY= "__dflt_position_category"; //$NON-NLS-1$ + + /** + * The identifier of the default partition content type. + */ + final static String DEFAULT_CONTENT_TYPE= "__dftl_partition_content_type"; //$NON-NLS-1$ + + + + + /* --------------- text access and manipulation --------------------------- */ + + /** + * Returns the character at the given document offset in this document. + * + * @param offset a document offset + * @return the character at the offset + * @exception BadLocationException if the offset is invalid in this document + */ + char getChar(int offset) throws BadLocationException; + + /** + * Returns the number of characters in this document. + * + * @return the number of characters in this document + */ + int getLength(); + + /** + * Returns this document's complete text. + * + * @return the document's complete text + */ + String get(); + + /** + * Returns this document's text for the specified range. + * + * @param offset the document offset + * @param length the length of the specified range + * @return the document's text for the specified range + * @exception BadLocationException if the range is invalid in this document + */ + String get(int offset, int length) throws BadLocationException; + + /** + * Replaces the content of the document with the given text. + * Sends a <code>DocumentEvent</code> to all registered <code>IDocumentListener</code>. + * This method is a convenience method for <code> + * replace(0, getLength(), text)</code>. + * + * @param text the new content of the document + * + * @see DocumentEvent + * @see IDocumentListener + */ + void set(String text); + + /** + * Subsitutes the given text for the specified document range. + * Sends a <code>DocumentEvent</code> to all registered <code>IDocumentListener</code>. + * + * @param offset the document offset + * @param length the length of the specified range + * @param text the substitution text + * @exception BadLocationException if the offset is invalid in this document + * + * @see DocumentEvent + * @see IDocumentListener + */ + void replace(int offset, int length, String text) throws BadLocationException; + + /** + * Registers the document listener with the document. After registration + * the IDocumentListener is informed about each change of this document. + * If the listener is already registered nothing happens.<p> + * An <code>IDocumentListener</code> may call back to this method + * when being inside a document notification. + * + * @param listener the listener to be registered + */ + void addDocumentListener(IDocumentListener listener); + + /** + * Removes the listener from the document's list of document listeners. + * If the listener is not registered with the document nothing happens.<p> + * An <code>IDocumentListener</code> may call back to this method + * when being inside a document notification. + * + * @param listener the listener to be removed + */ + void removeDocumentListener(IDocumentListener listener); + + /** + * Adds the given document listener as one which is notified before + * those document listeners added with <code>addDocumentListener</code> + * are notified. If the given listener is also registered using + * <code>addDocumentListener</code> it will be notified twice. + * If the listener is already registered nothing happens.<p> + * + * This method is not for public use, it may only be called by + * implementers of <code>IDocumentAdapter</code> and only if those + * implementers need to implement <code>IDocumentListener</code>. + * + * @param documentAdapter the listener to be added as prenotified document listener + */ + void addPrenotifiedDocumentListener(IDocumentListener documentAdapter); + + /** + * Removes the given document listener from the document's list of + * prenotified document listeners. If the listener is not registered + * with the document nothing happens. <p> + * + * This method is not for public use, it may only be called by + * implementers of <code>IDocumentAdapter</code> and only if those + * implementers need to implement <code>IDocumentListener</code>. + * + * @param documentAdapter the listener to be removed + * + * @see #addPrenotifiedDocumentListener(IDocumentListener) + */ + void removePrenotifiedDocumentListener(IDocumentListener documentAdapter); + + + + /* -------------------------- positions ----------------------------------- */ + + /** + * Adds a new position category to the document. If the position category + * already exists nothing happens. + * + * @param category the category to be added + */ + void addPositionCategory(String category); + + /** + * Deletes the position category from the document. All positions + * in this category are thus deleted as well. + * + * @param category the category to be removed + * @exception BadPositionCategoryException if category is undefined in this document + */ + void removePositionCategory(String category) throws BadPositionCategoryException; + + /** + * Returns all position categories of this document. This + * includes the default position category. + * + * @return the document's position categories + */ + String[] getPositionCategories(); + + /** + * Checks the presence of the specified position category. + * + * @param category the category to check + * @return <code>true</code> if category is defined + */ + boolean containsPositionCategory(String category); + + /** + * Adds the position to the document's default position category. + * This is a convenience method for <code>addPosition(DEFAULT_CATEGORY, position)</code>. + * + * @param position the position to be added + * @exception BadLocationException if position describes an invalid range in this document + */ + void addPosition(Position position) throws BadLocationException; + + /** + * Removes the given position from the document's default position category. + * This is a convenience method for <code>removePosition(DEFAULT_CATEGORY, position)</code>. + * + * @param position the position to be removed + */ + void removePosition(Position position); + + /** + * Adds the position to the specified position category of the document. + * A position that has been added to a position category is updated on each + * change applied to the document. Positions may be added multiple times. + * The order of the category is maintained. + * + * @param category the category to which to add + * @param position the position to be added + * @exception BadLocationException if position describes an invalid range in this document + * @exception BadPositionCategoryException if the category is undefined in this document + */ + void addPosition(String category, Position position) throws BadLocationException, BadPositionCategoryException; + + /** + * Removes the given position from the specified position category. + * If the position is not part of the specified category nothing happens. + * If the position has been added multiple times, only the first occurence is deleted. + * + * @param category the category from which to delete + * @param position the position to be deleted + * @exception BadPositionCategoryException if category is undefined in this document + */ + void removePosition(String category, Position position) throws BadPositionCategoryException; + + /** + * Returns all positions of the given position category. + * The positions are ordered according to the category's order. + * Manipulating this list does not affect the document, but manipulating the + * position does affect the document. + * + * @param category the category + * @return the list of all positions + * @exception BadPositionCategoryException if category is undefined in this document + */ + Position[] getPositions(String category) throws BadPositionCategoryException; + + /** + * Determines whether a position described by the parameters is managed by this document. + * + * @param category the category to check + * @param offset the offset of the position to find + * @param length the length of the position to find + * @return <code>true</code> if position is found + */ + boolean containsPosition(String category, int offset, int length); + + /** + * Computes the index at which a <code>Position</code> with the + * specified offset would be inserted into the given category. As the + * ordering inside a category only depends on the offset, the index must be + * choosen to be the first of all positions with the same offset. + * + * @param category the category in which would be added + * @param offset the position offset to be considered + * @return the index into the category + * @exception BadLocationException if offset is invalid in this document + * @exception BadPositionCategoryException if category is undefined in this document + */ + int computeIndexInCategory(String category, int offset) throws BadLocationException, BadPositionCategoryException; + + /** + * Appends a new position updater to the document's list of position updaters. + * Position updaters may be added multiple times.<p> + * An <code>IPositionUpdater</code> may call back to this method + * when being inside a document notification. + * + * @param updater the updater to be added + */ + void addPositionUpdater(IPositionUpdater updater); + + /** + * Removes the position updater from the document's list of position updaters. + * If the position updater has multiple occurences only the first occurence is + * removed. If the position updater is not registered with this document, nothing + * happens.<p> + * An <code>IPositionUpdater</code> may call back to this method + * when being inside a document notification. + * + * @param updater the updater to be removed + */ + void removePositionUpdater(IPositionUpdater updater); + + /** + * Inserts the position updater at the specified index in the document's + * list of position updaters. Positions updaters may be inserted multiple times.<p> + * An <code>IPositionUpdater</code> may call back to this method + * when being inside a document notification. + * + * @param updater the updater to be inserted + * @param index the index in the document's updater list + */ + void insertPositionUpdater(IPositionUpdater updater, int index); + + /** + * Returns the list of position updaters attached to the document. + * + * @return the list of position updaters + */ + IPositionUpdater[] getPositionUpdaters(); + + + + + /* -------------------------- partitions ---------------------------------- */ + + /** + * Returns the set of legal content types of document partitions. + * This set can be empty. The set can contain more content types than + * contained by the result of <code>getPartitioning(0, getLength())</code>. + * + * @return the set of legal content types + */ + String[] getLegalContentTypes(); + + /** + * Returns the type of the document partition containing the given offset. + * This is a convenience method for <code>getPartition(offset).getType()</code>. + * + * @param offset the document offset + * @return the partition type + * @exception BadLocationException if offset is invalid in this document + */ + String getContentType(int offset) throws BadLocationException; + + /** + * Returns the document partition in which the position is located. + * + * @param offset the document offset + * @return a specification of the partition + * @exception BadLocationException if offset is invalid in this document + */ + ITypedRegion getPartition(int offset) throws BadLocationException; + + /** + * Computes the partitioning of the given document range using the + * document's partitioner. + * + * @param offset the document offset at which the range starts + * @param length the length of the document range + * @return a specification of the range's partitioning + * @exception BadLocationException if the range is invalid in this document + */ + ITypedRegion[] computePartitioning(int offset, int length) throws BadLocationException; + + /** + * Registers the document partitioning listener with the document. After registration + * the document partitioning listener is informed about each partition change + * cause by a document manipulation or by changing the document's partitioner. + * If a document partitioning listener is also + * a document listener, the following notification sequence is guaranteed if a + * document manipulation changes the document partitioning: + * <ul> + * <li>listener.documentAboutToBeChanged(DocumentEvent); + * <li>listener.documentPartitioningChanged(); + * <li>listener.documentChanged(DocumentEvent); + * </ul> + * If the listener is already registered nothing happens.<p> + * An <code>IDocumentPartitioningListener</code> may call back to this method + * when being inside a document notification. + * + * @param listener the listener to be added + */ + void addDocumentPartitioningListener(IDocumentPartitioningListener listener); + + /** + * Removes the listener from this document's list of document partitioning + * listeners. If the listener is not registered with the document nothing + * happens.<p> + * An <code>IDocumentPartitioningListener</code> may call back to this method + * when being inside a document notification. + * + * @param listener the listener to be removed + */ + void removeDocumentPartitioningListener(IDocumentPartitioningListener listener); + + /** + * Sets this document's partitioner. The caller of this method is responsible for + * disconnecting the document's old partitioner from the document and to + * connect the new partitioner to the document. Informs all document partitioning + * listeners about this change. + * + * @param the document's new partitioner + * + * @see IDocumentPartitioningListener + */ + void setDocumentPartitioner(IDocumentPartitioner partitioner); + + /** + * Returns this document's partitioner. + * + * @return this document's partitioner + */ + IDocumentPartitioner getDocumentPartitioner(); + + + + /* ---------------------- line information -------------------------------- */ + + /** + * Returns the length of the given line including the line's delimiter. + * + * @param line the line of interest + * @return the length of the line + * @exception BadLocationException if the line number is invalid in this document + */ + int getLineLength(int line) throws BadLocationException; + + /** + * Returns the number of the line at which the character of the specified position is located. + * The first line has the line number 0. A new line starts directly after a line + * delimiter. <code>(pos == document length)</code> is a valid argument also there is no + * corresponding character. + * + * @param offset the document offset + * @return the number of the line + * @exception BadLocationException if the offset is invalid in this document + */ + int getLineOfOffset(int offset) throws BadLocationException; + + /** + * Determines the offset of the first character of the given line. + * + * @param line the line of interest + * @return the document offset + * @exception BadLocationException if the line number is invalid in this document + */ + int getLineOffset(int line) throws BadLocationException; + + /** + * Returns a description of the specified line. The line is described by its + * offset and its length excluding the line's delimiter. + * + * @param line the line of interest + * @return a line description + * @exception BadLocationException if the line number is invalid in this document + */ + IRegion getLineInformation(int line) throws BadLocationException; + + /** + * Returns a description of the line at the given offset. + * The description contains the offset and the length of the line + * excluding the line's delimiter. + * + * @param offset the offset whose line should be described + * @return a region describing the line + * @exception BadLocationException if offset is invalid in this document + */ + IRegion getLineInformationOfOffset(int offset) throws BadLocationException; + + /** + * Returns the number of lines in this document + * + * @return the number of lines in this document + */ + int getNumberOfLines(); + + /** + * Returns the number of lines which are occupied by a given text range. + * + * @param offset the offset of the specified text range + * @param length the length of the specified text range + * @return the number of lines occupied by the specified range + * @exception BadLocationException if specified range is invalid in this tracker + */ + int getNumberOfLines(int offset, int length) throws BadLocationException; + + /** + * Computes the number of lines in the given text. For a given + * implementer of this interface this method returns the same + * result as <code>set(text); getNumberOfLines()</code>. + * + * @param text the text whose number of lines should be computed + * @return the number of lines in the given text + */ + int computeNumberOfLines(String text); + + + /* ------------------ line delimiter conversion --------------------------- */ + + /** + * Returns the document's legal line delimiters. + * + * @return the document's legal line delimiters + */ + String[] getLegalLineDelimiters(); + + /** + * Returns the line delimiter of that line. + * + * @param line the line of interest + * @return the line delimiter of that line + * @exception BadLocationException if the line number is invalid in this document + */ + String getLineDelimiter(int line) throws BadLocationException; + + + + /* ---------------------------- search ------------------------------------ */ + + /** + * Returns the offset of a given search string in the document based on a set of search criteria. + * + * @param startOffset document offset at which search starts + * @param findString the string to find + * @param forwardSearch the search direction + * @param caseSensitive indicates whether lower and upper case should be distinguished + * @param wholeWord indicates whether the findString should be limited by white spaces as + * defined by Character.isWhiteSpace + * @return the offset of the first occurence of findString based on the parameters + * @exception BadLocationException if startOffset is an invalid document offset + */ + int search(int startOffset, String findString, boolean forwardSearch, boolean caseSensitive, boolean wholeWord) throws BadLocationException; +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentExtension.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentExtension.java new file mode 100644 index 000000000..2986e184b --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentExtension.java @@ -0,0 +1,81 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +/** + * Extension interface for <code>IDocument</code>. <p> + * It introduces the notion of sequentially rewriting a document. This is to tell a + * document that a sequence of non-overlapping replace operation is about to be + * performed.<p> + * Is also introduces the concept of post notification replaces. This is, a document + * listener who is informed about a document change can cause a derived document + * change. As the listener is not allowed to directly modify the document, it can + * register a replace operation that is performed directly after all document listeners + * have been notified. + * + * @since 2.0 + */ +public interface IDocumentExtension { + + /** + * Interface for a post notification replace operation. + */ + public interface IReplace { + + /** + * Executes the replace operation on the given document. + * @param document the document to be changed + * @param owner the owner of this replace operation + */ + void perform(IDocument document, IDocumentListener owner); + }; + + /** + * Callback for document listeners to be used inside <code>documentChanged</code> + * to register a post notification replace operation on the document notifying them. + * + * @param owner the owner of the replace operation + * @param the replace operation to be executed + * @exception UnsupportedOperationException if <code>registerPostNotificationReplace</code> + * is not supported by this document + */ + void registerPostNotificationReplace(IDocumentListener owner, IReplace replace) throws UnsupportedOperationException; + + /** + * Stops the processing of registered post notification replace operations until + * <code>resumePostNotificationProcessing</code> is called. + */ + void stopPostNotificationProcessing(); + + /** + * Resumes the processing of post notification replace operations. If the queue of registered + * <code>IDocumentExtension.IReplace</code> objects is not empty, they are immediately processed if the + * document is not inside a replace operation. If the document is inside a replace operation, + * they are processed directly after the replace operation has finished. + */ + void resumePostNotificationProcessing(); + + /** + * Tells the document that it is about to be sequentially rewritten. That is a + * sequence of non-overlapping replace operations will be performed on it. The + * <code>normalize</code> flag indicates whether the rewrite is preformed from + * the start of the document to its end or from an arbitrary start offset. + * + * @param normalize <code>true</code> if performed from the start to the end of the document + */ + void startSequentialRewrite(boolean normalize); + + /** + * Tells the document that the sequential rewrite has been finished. + */ + void stopSequentialRewrite(); +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentListener.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentListener.java new file mode 100644 index 000000000..dc957f81f --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentListener.java @@ -0,0 +1,33 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Interface for objects which are interested in getting informed about + * document changes. A listener is informed about document changes before + * they are applied and after they have been applied. It is ensured that + * the document event passed into the listener is the same for the two + * notifications, i.e. the two document events can be checked using object identity. + * Clients may implement this interface. + */ +public interface IDocumentListener { + + + /** + * The manipulation described by the document event will be performed. + * + * @param event the document event describing the document change + */ + void documentAboutToBeChanged(DocumentEvent event); + + /** + * The manipulation described by the document event has been performed. + * + * @param event the document event describing the document change + */ + void documentChanged(DocumentEvent event); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioner.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioner.java new file mode 100644 index 000000000..d1cff732d --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioner.java @@ -0,0 +1,104 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * A document partitioner divides a document into a set + * of disjoint text partitions. Each partition has a content type, an + * offset, and a length. The document partitioner is connected to one document + * and informed about all changes of this document before any of the + * document's document listeners. A document partitioner can thus + * incrementally update on the receipt of a document change event. + * Clients may implement this interface or use the standard + * implementation <code>RuleBasedDocumentPartitioner</code>. + * + * @see IDocument + */ +public interface IDocumentPartitioner { + + /** + * Connects the partitioner to a document. + * Connect indicates the begin of the usage of the receiver + * as partitioner of the given document. Thus, resources the partitioner + * needs to be operational for this document should be allocated.<p> + * The caller of this method must ensure that this partitioner is + * also set as the document's document partitioner. + * + * @param document the document to be connected to + */ + void connect(IDocument document); + + /** + * Disconnects the partitioner from the document it is connected to. + * Disconnect indicates the end of the usage of the receiver as + * partitioner of the connected document. Thus, resources the partitioner + * needed to be operation for its connected document should be deallocated.<p> + * The caller of this method should also must ensure that this partitioner is + * no longer the document's partitioner. + */ + void disconnect(); + + /** + * Informs about a forthcoming document change. Will be called by the + * connected document and is not intended to be used by clients + * other than the connected document. + * + * @param event the event describing the forthcoming change + */ + void documentAboutToBeChanged(DocumentEvent event); + + /** + * The document has been changed. The partitioner updates + * the document's partitioning and returns whether the structure of the + * document partitioning has been changed, i.e. whether partitions + * have been added or removed. Will be called by the connected document and + * is not intended to be used by clients other than the connected document. + * + * @param event the event describing the document change + * @return <code>true</code> if partitioning changed + */ + boolean documentChanged(DocumentEvent event); + + /** + * Returns the set of all legal content types of this partitioner. + * I.e. any result delivered by this partitioner may not contain a content type + * which would not be included in this method's result. + * + * @return the set of legal content types + */ + String[] getLegalContentTypes(); + + /** + * Returns the content type of the partition containing the + * given offset in the connected document. There must be a + * document connected to this partitioner. + * + * @param offset the offset in the connected document + * @return the content type of the offset's partition + */ + String getContentType(int offset); + + /** + * Returns the partitioning of the given range of the connected + * document. There must be a document connected to this partitioner. + * + * @param offset the offset of the range of interest + * @param length the length of the range of interest + * @return the partitioning of the range + */ + ITypedRegion[] computePartitioning(int offset, int length); + + /** + * Returns the partition containing the given offset of + * the connected document. There must be a document connected to this + * partitioner. + * + * @param offset the offset for which to determine the partition + * @return the partition containing the offset + */ + ITypedRegion getPartition(int offset); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitionerExtension.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitionerExtension.java new file mode 100644 index 000000000..34eda1f98 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitionerExtension.java @@ -0,0 +1,37 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Extension interface for <code>IDocumentPartitioner</code>. Extends the original + * concept of a document partitioner by returning the minimal region that includes all + * partition changes causes by the invocation of the document partitioner. + * + * @since 2.0 + +*/ +public interface IDocumentPartitionerExtension { + + /** + * The document has been changed. The partitioner updates + * the document's partitioning and returns in which region the + * partition types have changed. This method always returns + * the surrounding region. Will be called by the connected document + * and is not intended to be used by clients other than the connected + * document. + * + * @param event the event describing the document change + * @return the region of the document in which the partition type changed + */ + IRegion documentChanged2(DocumentEvent event); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java new file mode 100644 index 000000000..59e2cbabc --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListener.java @@ -0,0 +1,27 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Interface of objects which are interested in getting informed + * about changes of a document's partitioning. Clients may + * implement this interface. + * + * @see IDocument + * @see IDocumentPartitioner + */ +public interface IDocumentPartitioningListener { + + /** + * The partitioning of the given document changed. + * + * @param document the document whose partitioning changed + * + * @see IDocument#addDocumentPartitioningListener + */ + void documentPartitioningChanged(IDocument document); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListenerExtension.java b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListenerExtension.java new file mode 100644 index 000000000..3ae89d441 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IDocumentPartitioningListenerExtension.java @@ -0,0 +1,33 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Extension interface for <code>IDocumentPartitioningListener</code>. Extends the original + * partitioning listener concept by telling the listener the minimal region that comprises all + * partitioning changes. + * + * @see org.eclipse.jface.text.IDocumentPartitionerExtension + * @since 2.0 + */ +public interface IDocumentPartitioningListenerExtension { + + /** + * The partitioning of the given document changed in the given region. + * + * @param document the document whose partitioning changed + * @param region the region in which the partitioning changed + * @see IDocument#addDocumentPartitioningListener + */ + void documentPartitioningChanged(IDocument document, IRegion region); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ILineTracker.java b/org.eclipse.text/src/org/eclipse/jface/text/ILineTracker.java new file mode 100644 index 000000000..e800841e5 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ILineTracker.java @@ -0,0 +1,132 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +import java.util.Iterator; + +/** + * A line tracker maps character positions to line numbers and vice versa. + * Initially the line tracker is informed about its underlying text which + * in order to initialize the mapping information. After that, the line + * tracker is informed about all changes of the underlying text allowing + * for incremental updates of the mapping information. It is the client's + * responsibility to actively inform the line tacker about text changes. + * For example, when using a line tracker in combination with a document + * the document controls the line tracker.<p> + * Clients may implement this interface or use the standard implementation + * <code>DefaultLineTracker</code>. + */ +public interface ILineTracker { + + /** + * Returns the strings this tracker considers as legal line delimiters. + * + * @return the legal line delimiters + */ + String[] getLegalLineDelimiters(); + + /** + * Returns the line delimiter of the specified line. Returns null if the + * line is not closed with a line delimiter. + * + * @param line the line whose line delimiter is queried + * @return the line's delimiter or <code>null</code> if line does not have a delimiter + * @exception BadLocationException if the line number is invalid in this tracker's line structure + */ + String getLineDelimiter(int line) throws BadLocationException; + + /** + * Computes the number of lines in the given text. + * + * @param text the text whose number of lines should be computed + * @return the number of lines in the given text + */ + int computeNumberOfLines(String text); + + /** + * Returns the number of lines. + * + * @return the number of lines in this tracker's line structure + */ + int getNumberOfLines(); + + /** + * Returns the number of lines which are occupied by a given text range. + * + * @param offset the offset of the specified text range + * @param length the length of the specified text range + * @return the number of lines occupied by the specified range + * @exception BadLocationException if specified range is unknown to this tracker + */ + int getNumberOfLines(int offset, int length) throws BadLocationException; + + /** + * Returns the position of the first character of the specified line. + * + * @param line the line of interest + * @return offset of the first character of the line + * @exception BadLocationException if the line is unknown to this tracker + */ + int getLineOffset(int line) throws BadLocationException; + + /** + * Returns length of the specified line including the line's delimiter. + * + * @param line the line of interest + * @return the length of the line + * @exception BadLocationException if line is unknown to this tracker + */ + int getLineLength(int line) throws BadLocationException; + + /** + * Returns the line number the character at the given offset belongs to. + * + * @param offset the offset whose line number to be determined + * @return the number of the line the offset is on + * @exception BadLocationException if the offset is invalid in this tracker + */ + int getLineNumberOfOffset(int offset) throws BadLocationException; + + /** + * Returns a line description of the line at the given offset. + * The description contains the start offset and the length of the line + * excluding the line's delimiter. + * + * @param offset the offset whose line should be described + * @return a region describing the line + * @exception BadLocationException if offset is invalid in this tracker + */ + IRegion getLineInformationOfOffset(int offset) throws BadLocationException; + + /** + * Returns a line description of the given line. The description + * contains the start offset and the length of the line excluding the line's + * delimiter. + * + * @param line the line that should be described + * @return a region describing the line + * @exception BadLocationException if line is unknown to this tracker + */ + IRegion getLineInformation(int line) throws BadLocationException; + + /** + * Informs the line tracker about the specified change in the tracked text. + * + * @param offset the offset of the replaced text + * @param length the length of the replaced text + * @param text the substitution text + * @exception BadLocationException if specified range is unknown to this tracker + */ + void replace(int offset, int length, String text) throws BadLocationException; + + /** + * Sets the tracked text to the specified text. + * + * @param text the new tracked text + */ + void set(String text); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IPositionUpdater.java b/org.eclipse.text/src/org/eclipse/jface/text/IPositionUpdater.java new file mode 100644 index 000000000..590b57db4 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IPositionUpdater.java @@ -0,0 +1,33 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +/** + * A position updater is responsible for adapting document positions. + * When installed on a document, the position updater updates the + * document's positions to changes applied to this document. Document + * updaters can be selective, i.e. they might only update positions of + * a certain category.<p> + * Position updaters are of primary importance for the definition of + * the semantics of positions.<p> + * Clients may implement this interface or use the standard implementation + * <code>DefaultPositionUpdater</code>. + * + * @see IDocument + * @see Position + */ +public interface IPositionUpdater { + + /** + * Adapts positions to the change specified by the document event. + * It is ensured that the document's partitioning has been adapted to + * this document change and that all the position updaters which have + * a smaller index in the document's position updater list have been called. + * + * @param event the document event describing the document change + */ + void update(DocumentEvent event); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/IRegion.java b/org.eclipse.text/src/org/eclipse/jface/text/IRegion.java new file mode 100644 index 000000000..d3edc6740 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/IRegion.java @@ -0,0 +1,32 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +/** + * A region describes a certain range in an indexed text store. + * Text stores are for example documents or strings. A region is + * defined by its offset into the text store and its length.<p> + * A region is considered a value object. Its offset or length + * do not change over time. <p> + * Clients may implement this interface or use the standard implementation + * <code>Region</code>. + */ +public interface IRegion { + + /** + * Returns the length of the region. + * + * @return the length of the region + */ + int getLength(); + + /** + * Returns the offset of the region. + * + * @return the offset of the region + */ + int getOffset(); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ITextStore.java b/org.eclipse.text/src/org/eclipse/jface/text/ITextStore.java new file mode 100644 index 000000000..a82894bdf --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ITextStore.java @@ -0,0 +1,57 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + +/** + * Interface for storing and managing text. + * Provides access to the stored text and allows to manipulate it. + * Clients may implement this interface. + */ +public interface ITextStore { + + /** + * Returns the character at the specified offset. + * + * @param offset the offset in this text store + * @return the character at this offset + */ + char get(int offset); + + /** + * Returns the text of the specified character range. + * + * @param offset the offset of the range + * @param length the length of the range + * @return the text of the range + */ + String get(int offset, int length); + + /** + * Returns number of characters stored in this text store. + * + * @return the number of characters stored in this text store + */ + int getLength(); + + /** + * Replaces the specified character range with the given text. + * <code>replace(getLength(), 0, "some text")</code> is a valid + * call and appends text to the end of the text store. + * + * @param offset the offset of the range to be replaced + * @param length the number of characters to be replaced + * @param text the substitution text + */ + void replace(int offset, int length, String text); + + /** + * Replace the content of the text store with the given text. + * Convenience method for <code>replace(0, getLength(), text</code>. + * + * @param text the new content of the text store + */ + void set(String text); +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/ITypedRegion.java b/org.eclipse.text/src/org/eclipse/jface/text/ITypedRegion.java new file mode 100644 index 000000000..9cd573a83 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/ITypedRegion.java @@ -0,0 +1,24 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Describes a region of an indexed text store such as document or string. + * The region consists of offset, length, and type. The type is defines as + * a string. A typed region can, e.g., be used to described document partitions. + * Clients may implement this interface or use the standard impementation + * <code>TypedRegion</code>. + */ +public interface ITypedRegion extends IRegion { + + /** + * Returns the content type of the region. + * + * @return the content type of the region + */ + String getType(); +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/Line.java b/org.eclipse.text/src/org/eclipse/jface/text/Line.java new file mode 100644 index 000000000..ab39bfd17 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/Line.java @@ -0,0 +1,63 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Describes a line as a particular number of characters beginning at + * a particular offset, consisting of a particular number of characters, + * and being closed with a particular line delimiter. + */ +class Line implements IRegion { + + /** The offset of the line */ + public int offset; + /** The length of the line */ + public int length; + /** The delimiter of this line */ + public String delimiter; + + /** + * Creates a new Line. + * + * @param offset the offset of the line + * @param end the last including character offset of the line + * @param delimiter the line's delimiter + */ + public Line(int offset, int end, String delimiter) { + this.offset= offset; + this.length= (end - offset) +1; + this.delimiter= delimiter; + } + + /** + * Creates a new Line. + * + * @param offset the offset of the line + * @param length the length of the line + */ + public Line(int offset, int length) { + this.offset= offset; + this.length= length; + this.delimiter= null; + } + + /* + * @see IRegion#getOffset() + */ + public int getOffset() { + return offset; + } + + /* + * @see IRegion#getLength() + */ + public int getLength() { + return length; + } +} + + diff --git a/org.eclipse.text/src/org/eclipse/jface/text/Position.java b/org.eclipse.text/src/org/eclipse/jface/text/Position.java new file mode 100644 index 000000000..08737da6d --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/Position.java @@ -0,0 +1,181 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + + +/** + * Positions describe text ranges of a document and are + * adapted to changes applied to that document. The text range + * is specified by an offset and a length. Positions can be marked as + * deleted. Deleted positions are considered to no longer represent + * a valid text range in the managing document. <p> + * Positions attached to documents are ususally updated by position updaters. + * Because position updaters are freely defineable and because of the + * frequence in which they are used, the fields of a position are made + * publicly accessible. Clients other than position updaters are not + * allowed to access these public fields. + * + * @see IDocument + */ +public class Position { + + /** The offset of the position */ + public int offset; + /** The length of the position */ + public int length; + /** Indicates whether the position has been deleted */ + public boolean isDeleted; + + /** + * Creates a new position with the given offset and length 0. + * + * @param offset the position offset, must be >= 0 + */ + public Position(int offset) { + this(offset, 0); + } + + /** + * Creates a new position with the given offset and length. + * + * @param offset the position offset, must be >= 0 + * @param length the position length, must be >= 0 + */ + public Position(int offset, int length) { + Assert.isTrue(offset >= 0); + Assert.isTrue(length >= 0); + this.offset= offset; + this.length= length; + } + + /* + * @see Object#hashCode + */ + public int hashCode() { + int deleted= isDeleted ? 0 : 1; + return (offset << 24) | (length << 16) | deleted; + } + + /** + * Marks this position as deleted. + */ + public void delete() { + isDeleted= true; + } + + /** + * Marks this position as not deleted. + * @since 2.0 + */ + public void undelete() { + isDeleted= false; + } + + /* + * @see Object#equals + */ + public boolean equals(Object other) { + if (other instanceof Position) { + Position rp= (Position) other; + return (rp.offset == offset) && (rp.length == length); + } + return super.equals(other); + } + + /** + * Returns the length of this position. + * + * @return the length of this position + */ + public int getLength() { + return length; + } + + /** + * Returns the offset of this position. + * + * @return the length of this position + */ + public int getOffset() { + return offset; + } + + /** + * Checks whether the given offset is inside + * of this position's text range. + * + * @param offset the offset to check + * @return <code>true</code> if offset is inside of this position + */ + public boolean includes(int offset) { + + if (isDeleted) + return false; + + return (this.offset <= offset) && (offset < this.offset + length); + } + + /** + * Checks whether the intersection of the given text range + * and the text range represented by this position is empty + * or not. + * + * @param offset the offset of the range to check + * @param length the length of the range to check + * @return <code>true</code> if intersection is not empty + */ + public boolean overlapsWith(int offset, int length) { + + if (isDeleted) + return false; + + int end= offset + length; + int thisEnd= this.offset + this.length; + + if (length > 0) { + if (this.length > 0) + return this.offset < end && offset < thisEnd; + return offset <= this.offset && this.offset < end; + } + + if (this.length > 0) + return this.offset <= offset && offset < thisEnd; + return this.offset == offset; + } + + /** + * Returns whether this position has been deleted or not. + * + * @return <code>true</code> if position has been deleted + */ + public boolean isDeleted() { + return isDeleted; + } + + /** + * Changes the length of this position to the given length. + * + * @param length the new length of this position + */ + public void setLength(int length) { + this.length= length; + } + + /** + * Changes the offset of this position to the given offset. + * + * @param offset the new offset of this position + */ + public void setOffset(int offset) { + this.offset= offset; + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/Region.java b/org.eclipse.text/src/org/eclipse/jface/text/Region.java new file mode 100644 index 000000000..98a65d9bb --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/Region.java @@ -0,0 +1,63 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * A default implementation of the <code>IRegion</code> interface. + */ +public class Region implements IRegion { + + /** The region offset */ + private int fOffset; + /** The region length */ + private int fLength; + + /** + * Create a new region. + * + * @param offset the offset of the region + * @param length the length of the region + */ + public Region(int offset, int length) { + fOffset= offset; + fLength= length; + } + + /* + * @see IRegion#getLength + */ + public int getLength() { + return fLength; + } + + /* + * @see IRegion#getOffset + */ + public int getOffset() { + return fOffset; + } + + /** + * Two regions are equal if they have the same offset and length. + * + * @see Object#equals + */ + public boolean equals(Object o) { + if (o instanceof IRegion) { + IRegion r= (IRegion) o; + return r.getOffset() == fOffset && r.getLength() == fLength; + } + return false; + } + + /* + * @see Object#hashCode + */ + public int hashCode() { + return (fOffset << 24) | (fLength << 16); + } +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/SequentialRewriteTextStore.java b/org.eclipse.text/src/org/eclipse/jface/text/SequentialRewriteTextStore.java new file mode 100644 index 000000000..9856a272c --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/SequentialRewriteTextStore.java @@ -0,0 +1,273 @@ +/********************************************************************** +Copyright (c) 2000, 2002 IBM Corp. and others. +All rights reserved. This program and the accompanying materials +are made available under the terms of the Common Public License v1.0 +which accompanies this distribution, and is available at +http://www.eclipse.org/legal/cpl-v10.html + +Contributors: + IBM Corporation - Initial implementation +**********************************************************************/ + +package org.eclipse.jface.text; + +import java.util.Iterator; +import java.util.LinkedList; +import java.util.List; + + +/** + * A text store that optimizes a given source text store for sequential rewriting. + * While rewritten it keeps a list of replace command that serve as patches for + * the source store. Only on request, the source store is indeed manipulated + * by applying the patch commands to the source text store. + * + * @since 2.0 + */ +public class SequentialRewriteTextStore implements ITextStore { + + /** + * A buffered replace command. + */ + private static class Replace { + public int newOffset; + public final int offset; + public final int length; + public final String text; + + public Replace(int offset, int newOffset, int length, String text) { + this.newOffset= newOffset; + this.offset= offset; + this.length= length; + this.text= text; + } + } + + /** The list of buffered replacements. */ + private List fReplaceList; + /** The source text store */ + private ITextStore fSource; + /** A flag to enforce sequential access. */ + private static final boolean ASSERT_SEQUENTIALITY= false; + + + /** + * Creates a new sequential rewrite store for the given source store. + * + * @param source the source text store + */ + public SequentialRewriteTextStore(ITextStore source) { + fReplaceList= new LinkedList(); + fSource= source; + } + + /** + * Returns the source store of this rewrite store. + * + * @return the source store of this rewrite store + */ + public ITextStore getSourceStore() { + commit(); + return fSource; + } + + /* + * @see ITextStore#replace(int, int, String) + */ + public void replace(int offset, int length, String text) { + + if (fReplaceList.size() == 0) { + fReplaceList.add(new Replace(offset, offset, length, text)); + + } else { + Replace firstReplace= (Replace) fReplaceList.get(0); + Replace lastReplace= (Replace) fReplaceList.get(fReplaceList.size() - 1); + + // backward + if (offset + length <= firstReplace.newOffset) { + int delta= text.length() - length; + if (delta != 0) { + for (Iterator i= fReplaceList.iterator(); i.hasNext(); ) { + Replace replace= (Replace) i.next(); + replace.newOffset += delta; + } + } + + fReplaceList.add(0, new Replace(offset, offset, length, text)); + + // forward + } else if (offset >= lastReplace.newOffset + lastReplace.text.length()) { + int delta= getDelta(lastReplace); + fReplaceList.add(new Replace(offset - delta, offset, length, text)); + + } else if (ASSERT_SEQUENTIALITY) { + throw new IllegalArgumentException(); + + } else { + commit(); + fSource.replace(offset, length, text); + } + } + } + + /* + * @see ITextStore#set(String) + */ + public void set(String text) { + fSource.set(text); + fReplaceList.clear(); + } + + /* + * @see ITextStore#get(int, int) + */ + public String get(int offset, int length) { + + if (fReplaceList.size() == 0) { + return fSource.get(offset, length); + + } else { + Replace firstReplace= (Replace) fReplaceList.get(0); + Replace lastReplace= (Replace) fReplaceList.get(fReplaceList.size() - 1); + + // before + if (offset + length <= firstReplace.newOffset) { + return fSource.get(offset, length); + + // after + } else if (offset >= lastReplace.newOffset + lastReplace.text.length()) { + int delta= getDelta(lastReplace); + return fSource.get(offset - delta, length); + + } else if (ASSERT_SEQUENTIALITY) { + throw new IllegalArgumentException(); + + } else { + + int delta= 0; + for (Iterator i= fReplaceList.iterator(); i.hasNext(); ) { + Replace replace= (Replace) i.next(); + + if (offset + length < replace.newOffset) { + return fSource.get(offset - delta, length); + + } else if (offset >= replace.newOffset && offset + length <= replace.newOffset + replace.text.length()) { + return replace.text.substring(offset - replace.newOffset, length); + + } else if (offset >= replace.newOffset + replace.text.length()) { + delta= getDelta(replace); + continue; + + } else { + commit(); + return fSource.get(offset, length); + } + } + + return fSource.get(offset - delta, length); + } + } + } + + /** + * Returns the difference between the offset in the source store and the "same" offset in the + * rewrite store after the replace operation. + * + * @param replace the replace command + */ + private static final int getDelta(Replace replace) { + return replace.newOffset - replace.offset + replace.text.length() - replace.length; + } + + /* + * @see ITextStore#get(int) + */ + public char get(int offset) { + if (fReplaceList.size() == 0) { + return fSource.get(offset); + + } else { + Replace firstReplace= (Replace) fReplaceList.get(0); + Replace lastReplace= (Replace) fReplaceList.get(fReplaceList.size() - 1); + + // before + if (offset < firstReplace.newOffset) { + return fSource.get(offset); + + // after + } else if (offset >= lastReplace.newOffset + lastReplace.text.length()) { + int delta= getDelta(lastReplace); + return fSource.get(offset - delta); + + } else if (ASSERT_SEQUENTIALITY) { + throw new IllegalArgumentException(); + + } else { + + int delta= 0; + for (Iterator i= fReplaceList.iterator(); i.hasNext(); ) { + Replace replace= (Replace) i.next(); + + if (offset < replace.newOffset) + return fSource.get(offset - delta); + + else if (offset < replace.newOffset + replace.text.length()) + return replace.text.charAt(offset - replace.newOffset); + + delta= getDelta(replace); + } + + return fSource.get(offset - delta); + } + } + } + + /* + * @see ITextStore#getLength() + */ + public int getLength() { + if (fReplaceList.size() == 0) { + return fSource.getLength(); + + } else { + Replace lastReplace= (Replace) fReplaceList.get(fReplaceList.size() - 1); + return fSource.getLength() + getDelta(lastReplace); + } + } + + /** + * Disposes this rewrite store. + */ + public void dispose() { + fReplaceList= null; + fSource= null; + } + + /** + * Commits all buffered replace commands. + */ + private void commit() { + + if (fReplaceList.size() == 0) + return; + + StringBuffer buffer= new StringBuffer(); + + int delta= 0; + for (Iterator i= fReplaceList.iterator(); i.hasNext(); ) { + Replace replace= (Replace) i.next(); + + int offset= buffer.length() - delta; + buffer.append(fSource.get(offset, replace.offset - offset)); + buffer.append(replace.text); + delta= getDelta(replace); + } + + int offset= buffer.length() - delta; + buffer.append(fSource.get(offset, fSource.getLength() - offset)); + + fSource.set(buffer.toString()); + fReplaceList.clear(); + } + +} diff --git a/org.eclipse.text/src/org/eclipse/jface/text/TextUtilities.java b/org.eclipse.text/src/org/eclipse/jface/text/TextUtilities.java new file mode 100644 index 000000000..9aea7a4e0 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/TextUtilities.java @@ -0,0 +1,117 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Collection of text functions. + */ +public class TextUtilities { + + + public final static String[] fgDelimiters= new String[] { "\n", "\r", "\r\n" }; //$NON-NLS-3$ //$NON-NLS-2$ //$NON-NLS-1$ + + + /** + * Determines which one of fgDelimiters appears first in the list. If none of them the + * hint is returned. + */ + public static String determineLineDelimiter(String text, String hint) { + try { + int[] info= indexOf(fgDelimiters, text, 0); + return fgDelimiters[info[1]]; + } catch (ArrayIndexOutOfBoundsException x) { + } + return hint; + } + + /** + * Returns the position in the string greater than offset + * of the longest matching search string. + */ + public static int[] indexOf(String[] searchStrings, String text, int offset) { + + int[] result= { -1, -1 }; + int zeroIndex= -1; + + for (int i= 0; i < searchStrings.length; i++) { + + int length= searchStrings[i].length(); + + if (length == 0) { + zeroIndex= i; + continue; + } + + int index= text.indexOf(searchStrings[i], offset); + if (index >= 0) { + + if (result[0] == -1) { + result[0]= index; + result[1]= i; + } else if (index < result[0]) { + result[0]= index; + result[1]= i; + } else if (index == result[0] && length > searchStrings[result[1]].length()) { + result[0]= index; + result[1]= i; + } + } + } + + if (zeroIndex > -1 && result[0] == -1) { + result[0]= 0; + result[1]= zeroIndex; + } + + return result; + } + + /** + * Returns the longest search string with which the given text ends. + */ + public static int endsWith(String[] searchStrings, String text) { + + int index= -1; + + for (int i= 0; i < searchStrings.length; i++) { + if (text.endsWith(searchStrings[i])) { + if (index == -1 || searchStrings[i].length() > searchStrings[index].length()) + index= i; + } + } + + return index; + } + + /** + * Returns the longest search string with which the given text starts. + */ + public static int startsWith(String[] searchStrings, String text) { + + int index= -1; + + for (int i= 0; i < searchStrings.length; i++) { + if (text.startsWith(searchStrings[i])) { + if (index == -1 || searchStrings[i].length() > searchStrings[index].length()) + index= i; + } + } + + return index; + } + + /** + * Returns whether the text equals one of the given compare strings. + */ + public static int equals(String[] compareStrings, String text) { + for (int i= 0; i < compareStrings.length; i++) { + if (text.equals(compareStrings[i])) + return i; + } + return -1; + } +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/TypedPosition.java b/org.eclipse.text/src/org/eclipse/jface/text/TypedPosition.java new file mode 100644 index 000000000..bec400808 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/TypedPosition.java @@ -0,0 +1,68 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Convenience class for position like typed regions. + */ +public class TypedPosition extends Position { + + /** The type of the region described by this position */ + private String fType; + + /** + * Creates a position along the given specification. + * + * @param offset the offset of this position + * @param length the length of this position + * @param type the type of this position + */ + public TypedPosition(int offset, int length, String type) { + super(offset, length); + fType= type; + } + + /** + * Creates a position based on the typed region. + * + * @param region the typed region + */ + public TypedPosition(ITypedRegion region) { + super(region.getOffset(), region.getLength()); + fType= region.getType(); + } + + /** + * Returns the type of the position + * + * @return the type of this position + */ + public String getType() { + return fType; + } + + /* + * @see Object#equals + */ + public boolean equals(Object o) { + if (o instanceof TypedPosition) { + if (super.equals(o)) { + TypedPosition p= (TypedPosition) o; + return (fType == null && p.getType() == null) || fType.equals(p.getType()); + } + } + return false; + } + + /* + * @see Object#hashCode + */ + public int hashCode() { + int type= fType == null ? 0 : fType.hashCode(); + return super.hashCode() | type; + } +}
\ No newline at end of file diff --git a/org.eclipse.text/src/org/eclipse/jface/text/TypedRegion.java b/org.eclipse.text/src/org/eclipse/jface/text/TypedRegion.java new file mode 100644 index 000000000..f45eeb653 --- /dev/null +++ b/org.eclipse.text/src/org/eclipse/jface/text/TypedRegion.java @@ -0,0 +1,56 @@ +package org.eclipse.jface.text; + +/* + * (c) Copyright IBM Corp. 2000, 2001. + * All Rights Reserved. + */ + + +/** + * Default implementation of <code>ITypedRegion</code>. + */ +public class TypedRegion extends Region implements ITypedRegion { + + /** The region's type */ + private String fType; + + /** + * Creates a typed region based on the given specification. + * + * @param offset the region's offset + * @param length the region's length + * @param type the region's type + */ + public TypedRegion(int offset, int length, String type) { + super(offset, length); + fType= type; + } + + /* + * @see ITypedRegion#getType() + */ + public String getType() { + return fType; + } + + /** + * Two typed positions are equal if they have the same offset, length, and type. + * + * @see Object#equals + */ + public boolean equals(Object o) { + if (o instanceof TypedRegion) { + TypedRegion r= (TypedRegion) o; + return super.equals(r) && ((fType == null && r.getType() == null) || fType.equals(r.getType())); + } + return false; + } + + /* + * @see Object#hashCode + */ + public int hashCode() { + int type= fType == null ? 0 : fType.hashCode(); + return super.hashCode() | type; + } +}
\ No newline at end of file |