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