*** empty log message ***
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / corext / textmanipulation / TextBuffer.java
1 /*
2  * (c) Copyright IBM Corp. 2000, 2001.
3  * All Rights Reserved.
4  */
5 package net.sourceforge.phpdt.internal.corext.textmanipulation;
6
7 import java.util.ArrayList;
8 import java.util.List;
9
10 import net.sourceforge.phpdt.internal.corext.util.Strings;
11 import net.sourceforge.phpdt.internal.ui.PHPStatusConstants;
12 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
13
14 import org.eclipse.core.resources.IFile;
15 import org.eclipse.core.runtime.CoreException;
16 import org.eclipse.core.runtime.IProgressMonitor;
17 import org.eclipse.core.runtime.IStatus;
18 import org.eclipse.core.runtime.Status;
19 import org.eclipse.jface.text.BadLocationException;
20 import org.eclipse.jface.text.DefaultLineTracker;
21 import org.eclipse.jface.text.IDocument;
22 import org.eclipse.jface.text.IDocumentListener;
23 import org.eclipse.jface.text.ILineTracker;
24 import org.eclipse.jface.text.IRegion;
25 import org.eclipse.jface.util.Assert;
26
27 //import org.eclipse.jdt.internal.ui.JavaPlugin;
28 //import org.eclipse.jdt.internal.ui.JavaStatusConstants;
29
30 /**
31  * An implementation of a <code>TextBuffer</code> that is based on <code>ITextSelection</code>
32  * and <code>IDocument</code>.
33  */
34 public class TextBuffer {
35
36         private static class DocumentRegion extends TextRegion {
37                 IRegion fRegion;
38                 public DocumentRegion(IRegion region) {
39                         fRegion= region;
40                 }
41                 public int getOffset() {
42                         return fRegion.getOffset();
43                 }
44                 public int getLength() {
45                         return fRegion.getLength();
46                 }
47         }
48         
49         public class Block {
50                 public String content;
51                 public int offsetDelta;
52         }
53         
54         private IDocument fDocument;
55         
56         private static final TextBufferFactory fgFactory= new TextBufferFactory();
57         
58         TextBuffer(IDocument document) {
59                 fDocument= document;
60                 Assert.isNotNull(fDocument);
61         }
62         
63         /**
64          * Returns the number of characters in this text buffer.
65          *
66          * @return the number of characters in this text buffer
67          */
68         public int getLength() {
69                 return fDocument.getLength();
70         }
71         
72         /**
73          * Returns the number of lines in this text buffer.
74          * 
75          * @return the number of lines in this text buffer
76          */
77         public int getNumberOfLines() {
78                 return fDocument.getNumberOfLines();
79         }
80         
81         /**
82          * Returns the character at the given offset in this text buffer.
83          *
84          * @param offset a text buffer offset
85          * @return the character at the offset
86          * @exception  IndexOutOfBoundsException  if the <code>offset</code> 
87          *  argument is negative or not less than the length of this text buffer.
88          */
89         public char getChar(int offset) {
90                 try {
91                         return fDocument.getChar(offset);
92                 } catch (BadLocationException e) {
93                         throw new ArrayIndexOutOfBoundsException(e.getMessage());
94                 }
95         }
96         
97         /**
98          * Returns the whole content of the text buffer.
99          *
100          * @return the whole content of the text buffer
101          */
102         public String getContent() {
103                 return fDocument.get();
104         }
105         
106         /**
107          * Returns length characters starting from the specified position.
108          *
109          * @return the characters specified by the given text region. Returns <code>
110          *  null</code> if text range is illegal
111          */
112         public String getContent(int start, int length) {
113                 try {
114                         return fDocument.get(start, length);
115                 } catch (BadLocationException e) {
116                         return null;
117                 }
118         }
119         
120         public Block getBlockContent(int start, int length, int tabWidth) {
121                 Block result= new Block();
122                 StringBuffer buffer= new StringBuffer();
123                 int lineOffset= getLineInformationOfOffset(start).getOffset();
124                 if (start > lineOffset) {
125                         String line= getContent(lineOffset, start - lineOffset);
126                         String indent= Strings.getIndentString(line, tabWidth);
127                         result.offsetDelta= -indent.length();
128                         buffer.append(indent);
129                 }
130                 final int end= start + length;
131                 TextRegion region= getLineInformationOfOffset(end);
132                 lineOffset= region.getOffset();
133                 // Cursor is at beginning of next line
134                 if (lineOffset == end) {
135                         int lineNumber= getLineOfOffset(lineOffset);
136                         if (lineNumber > 0) {
137                                 length= length - getLineDelimiter(lineNumber - 1).length();
138                         }
139                 }
140                 if (buffer.length() == 0) {
141                         result.content= getContent(start, length);
142                 } else {
143                         buffer.append(getContent(start, length));
144                         result.content= buffer.toString();
145                 }
146                 return result;
147         }
148         
149         /**
150          * Returns the preferred line delimiter to be used for this text buffer.
151          * 
152          * @return the preferred line delimiter
153          */
154         public String getLineDelimiter() {
155                 String lineDelimiter= getLineDelimiter(0);
156                 if (lineDelimiter == null)
157                         lineDelimiter= System.getProperty("line.separator", "\n"); //$NON-NLS-1$ //$NON-NLS-2$
158                 return lineDelimiter;
159         }
160         
161         /**
162          * Returns the line delimiter used for the given line number. Returns <code>
163          * null</code> if the line number is out of range.
164          *
165          * @return the line delimiter used by the given line number or <code>null</code>
166          */
167         public String getLineDelimiter(int line) {
168                 try {
169                         return fDocument.getLineDelimiter(line);
170                 } catch (BadLocationException e) {
171                         return null;
172                 }       
173         }
174         
175         /**
176          * Returns the line for the given line number. If there isn't any line for
177          * the given line number, <code>null</code> is returned.
178          *
179          * @return the line for the given line number or <code>null</code>
180          */
181         public String getLineContent(int line) {
182                 try {
183                         IRegion region= fDocument.getLineInformation(line);
184                         return fDocument.get(region.getOffset(), region.getLength());
185                 } catch (BadLocationException e) {
186                         return null;
187                 }
188         }
189         
190         /**
191          * Returns the line indent for the given line. If there isn't any line for the
192          * given line number, <code>-1</code> is returned.
193          * 
194          * @return the line indent for the given line number of <code>-1</code>
195          */
196         public int getLineIndent(int lineNumber, int tabWidth) {
197                 return Strings.computeIndent(getLineContent(lineNumber), tabWidth);
198         }
199         
200         /**
201          * Returns a region of the specified line. The region contains  the offset and the 
202          * length of the line excluding the line's delimiter. Returns <code>null</code> 
203          * if the line doesn't exist.
204          *
205          * @param line the line of interest
206          * @return a line description or <code>null</code> if the given line doesn't
207          *  exist
208          */
209         public TextRegion getLineInformation(int line) {
210                 try {
211                         return new DocumentRegion(fDocument.getLineInformation(line));
212                 } catch (BadLocationException e) {
213                         return null;
214                 }       
215         }
216         
217         /**
218          * Returns a line region of the specified offset.  The region contains the offset and 
219          * the length of the line excluding the line's delimiter. Returns <code>null</code> 
220          * if the line doesn't exist.
221          *
222          * @param offset an offset into a line
223          * @return a line description or <code>null</code> if the given line doesn't
224          *  exist
225          */ 
226         public TextRegion getLineInformationOfOffset(int offset) {
227                 try {
228                         return new DocumentRegion(fDocument.getLineInformationOfOffset(offset));
229                 } catch (BadLocationException e) {
230                         return null;
231                 }       
232         }
233         
234         /**
235          * Returns the line number that contains the given position. If there isn't any
236          * line that contains the position, <code>null</code> is returned. The returned 
237          * string is a copy and doesn't contain the line delimiter.
238          *
239          * @return the line that contains the given offset or <code>null</code> if line
240          *  doesn't exist
241          */ 
242         public int getLineOfOffset(int offset) {
243                 try {
244                         return fDocument.getLineOfOffset(offset);
245                 } catch (BadLocationException e) {
246                         return -1;
247                 }
248         }
249
250         /**
251          * Returns the line that contains the given position. If there isn't any
252          * line that contains the position, <code>null</code> is returned. The returned 
253          * string is a copy and doesn't contain the line delimiter.
254          *
255          * @return the line that contains the given offset or <code>null</code> if line
256          *  doesn't exist
257          */ 
258         public String getLineContentOfOffset(int offset) {
259                 try {
260                         IRegion region= fDocument.getLineInformationOfOffset(offset);
261                         return fDocument.get(region.getOffset(), region.getLength());
262                 } catch (BadLocationException e) {
263                         return null;
264                 }
265         }
266
267         /**
268          * Converts the text determined by the region [offset, length] into an array of lines. 
269          * The lines are copies of the original lines and don't contain any line delimiter 
270          * characters.
271          *
272          * @return the text converted into an array of strings. Returns <code>null</code> if the 
273          *  region lies outside the source. 
274          */
275         public String[] convertIntoLines(int offset, int length, boolean lastNewLineCreateEmptyLine) {
276                 try {
277                         String text= fDocument.get(offset, length);
278                         ILineTracker tracker= new DefaultLineTracker();
279                         tracker.set(text);
280                         int size= tracker.getNumberOfLines();
281                         int lastLine= size - 1;
282                         List result= new ArrayList(size);
283                         for (int i= 0; i < size; i++) {
284                                 IRegion region= tracker.getLineInformation(i);
285                                 String line= getContent(offset + region.getOffset(), region.getLength());
286                                 if (i < lastLine || !"".equals(line) || lastNewLineCreateEmptyLine) //$NON-NLS-1$
287                                         result.add(line);
288                         }
289                         return (String[]) result.toArray(new String[result.size()]);
290                 } catch (BadLocationException e) {
291                         return null;
292                 }
293         }
294         
295         /**
296          * Subsitutes the given text for the specified text position
297          *
298          * @param offset the starting offset of the text to be replaced
299          * @param length the length of the text to be replaced
300          * @param text the substitution text
301      * @exception  CoreException  if the text position [offset, length] is invalid.      
302          */
303         public void replace(int offset, int length, String text) throws CoreException {
304                 try {
305                         fDocument.replace(offset, length, text);
306                 } catch (BadLocationException e) {
307                         IStatus s = new Status(IStatus.ERROR, PHPeclipsePlugin.getPluginId(), PHPStatusConstants.INTERNAL_ERROR, 
308                                 TextManipulationMessages.getFormattedString(
309                                         "TextBuffer.wrongRange",  //$NON-NLS-1$
310                                         new Object[] {new Integer(offset), new Integer(length) } ), e);
311                         throw new CoreException(s);
312                 }       
313         }
314         
315         public void replace(TextRange range, String text) throws CoreException {
316                 replace(range.fOffset, range.fLength, text);
317         }
318
319         //---- Special methods used by the <code>TextBufferEditor</code>
320         
321         /**
322          * Releases this text buffer.
323          */
324         /* package */ void release() {
325         }
326         
327         /* package */ void registerUpdater(IDocumentListener listener) {
328                 fDocument.addDocumentListener(listener);
329         }
330         
331         /* package */ void unregisterUpdater(IDocumentListener listener) {
332                 fDocument.removeDocumentListener(listener);
333         }
334                 
335         //---- Factory methods ----------------------------------------------------------------
336         
337         /**
338          * Acquires a text buffer for the given file. If a text buffer for the given
339          * file already exists, then that one is returned.
340          * 
341          * @param file the file for which a text buffer is requested
342          * @return a managed text buffer for the given file
343          * @exception CoreException if it was not possible to acquire the
344          *      text buffer
345          */
346         public static TextBuffer acquire(IFile file) throws CoreException {
347                 return fgFactory.acquire(file);
348         }
349         
350         /**
351          * Releases the given text buffer.
352          * 
353          * @param buffer the text buffer to be released
354          */
355         public static void release(TextBuffer buffer) {
356                 fgFactory.release(buffer);
357         }
358
359         /**
360          * Commits the changes made to the given text buffer to the underlying
361          * storage system.
362          * 
363          * @param buffer the text buffer containing the changes to be committed.
364          * @param force if <code>true</code> the text buffer is committed in any case.
365          *      If <code>false</code> the text buffer is <b>ONLY</b> committed if the client 
366          *      is the last one that holds a reference to the text buffer. Clients of this
367          *      method must make sure that they don't call this method from within an <code>
368          *  IWorkspaceRunnable</code>.
369          * @param pm the progress monitor used to report progress if committing is
370          *      necessary
371          */
372         public static void commitChanges(TextBuffer buffer, boolean force, IProgressMonitor pm) throws CoreException {
373                 fgFactory.commitChanges(buffer, force, pm);
374         }
375         
376         /**
377          * Creates a new <code>TextBuffer</code> for the given file. The returned
378          * buffer will not be managed. Any subsequent call to <code>create</code>
379          * with the same file will return a different text buffer.
380          * <p>
381          * If the file is currently open in a text editor, the editors content is copied into
382          * the returned <code>TextBuffer</code>. Otherwise the content is read from
383          * disk.
384          * 
385          * @param file the file for which a text buffer is to be created
386          * @return a new unmanaged text buffer
387          * @exception CoreException if it was not possible to create the text buffer
388          */
389         public static TextBuffer create(IFile file) throws CoreException {
390                 return fgFactory.create(file);
391         }
392         
393         /**
394          * Creates a new <code>TextBuffer</code> for the string. The returned
395          * buffer will not be managed. Any subsequent call to <code>create</code>
396          * with the identical string will return a different text buffer.
397          * 
398          * @param content the text buffer's content
399          * @return a new unmanaged text buffer
400          */
401         public static TextBuffer create(String content) {
402                 return fgFactory.create(content);
403         }
404         
405         // Unclear which methods are needed if we get the new save model. If optimal no
406         // save is needed at all.
407         
408         public static void save(TextBuffer buffer, IProgressMonitor pm) throws CoreException {
409                 fgFactory.save(buffer, pm);
410         }
411         
412         public static void aboutToChange(TextBuffer buffer) throws CoreException {
413                 fgFactory.aboutToChange(buffer);
414         }
415         
416         public static void changed(TextBuffer buffer) throws CoreException {
417                 fgFactory.changed(buffer);
418         }               
419 }