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