Fixed: malfunctioned "Remove trailing spaces on editor save"
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpeclipse / phpeditor / php / PHPAutoIndentStrategy.java
1 /**********************************************************************
2  Copyright (c) 2000, 2002 IBM Corp. and others.
3  All rights reserved. This program and the accompanying materials
4  are made available under the terms of the Common Public License v1.0
5  which accompanies this distribution, and is available at
6  http://www.eclipse.org/legal/cpl-v10.html
7
8  Contributors:
9  IBM Corporation - Initial implementation
10  www.phpeclipse.de
11  **********************************************************************/
12 package net.sourceforge.phpeclipse.phpeditor.php;
13
14 import org.eclipse.jface.text.BadLocationException;
15 import org.eclipse.jface.text.DefaultIndentLineAutoEditStrategy;
16 import org.eclipse.jface.text.DocumentCommand;
17 import org.eclipse.jface.text.IDocument;
18
19 /**
20  * Auto indent strategy sensitive to brackets.
21  */
22 public class PHPAutoIndentStrategy extends DefaultIndentLineAutoEditStrategy {
23
24         public PHPAutoIndentStrategy() {
25         }
26
27         /*
28          * (non-Javadoc) Method declared on IAutoIndentStrategy
29          */
30         public void customizeDocumentCommand(IDocument d, DocumentCommand c) {
31                 if (c.length == 0 && c.text != null && endsWithDelimiter(d, c.text))
32                         smartIndentAfterNewLine(d, c);
33                 else if ("}".equals(c.text)) {
34                         smartInsertAfterBracket(d, c);
35                 }
36         }
37
38         /**
39          * Returns whether or not the text ends with one of the given search
40          * strings.
41          */
42         private boolean endsWithDelimiter(IDocument d, String txt) {
43
44                 String[] delimiters = d.getLegalLineDelimiters();
45
46                 for (int i = 0; i < delimiters.length; i++) {
47                         if (txt.endsWith(delimiters[i]))
48                                 return true;
49                 }
50
51                 return false;
52         }
53
54         /**
55          * Returns the line number of the next bracket after end.
56          * 
57          * @returns the line number of the next matching bracket after end
58          * @param document -
59          *            the document being parsed
60          * @param line -
61          *            the line to start searching back from
62          * @param end -
63          *            the end position to search back from
64          * @param closingBracketIncrease -
65          *            the number of brackets to skip
66          */
67         protected int findMatchingOpenBracket(IDocument document, int line,
68                         int end, int closingBracketIncrease) throws BadLocationException {
69
70                 int start = document.getLineOffset(line);
71                 int brackcount = getBracketCount(document, start, end, false)
72                                 - closingBracketIncrease;
73
74                 // sum up the brackets counts of each line (closing brackets count
75                 // negative,
76                 // opening positive) until we find a line the brings the count to zero
77                 while (brackcount < 0) {
78                         line--;
79                         if (line < 0) {
80                                 return -1;
81                         }
82                         start = document.getLineOffset(line);
83                         end = start + document.getLineLength(line) - 1;
84                         brackcount += getBracketCount(document, start, end, false);
85                 }
86                 return line;
87         }
88
89         /**
90          * Returns the bracket value of a section of text. Closing brackets have a
91          * value of -1 and open brackets have a value of 1.
92          * 
93          * @returns the line number of the next matching bracket after end
94          * @param document -
95          *            the document being parsed
96          * @param start -
97          *            the start position for the search
98          * @param end -
99          *            the end position for the search
100          * @param ignoreCloseBrackets -
101          *            whether or not to ignore closing brackets in the count
102          */
103         private int getBracketCount(IDocument document, int start, int end,
104                         boolean ignoreCloseBrackets) throws BadLocationException {
105
106                 int begin = start;
107                 int bracketcount = 0;
108                 while (begin < end) {
109                         char curr = document.getChar(begin);
110                         begin++;
111                         switch (curr) {
112                         case '/':
113                                 if (begin < end) {
114                                         char next = document.getChar(begin);
115                                         if (next == '*') {
116                                                 // a comment starts, advance to the comment end
117                                                 begin = getCommentEnd(document, begin + 1, end);
118                                         } else if (next == '/') {
119                                                 // '//'-comment: nothing to do anymore on this line
120                                                 begin = end;
121                                         }
122                                 }
123                                 break;
124                         case '*':
125                                 if (begin < end) {
126                                         char next = document.getChar(begin);
127                                         if (next == '/') {
128                                                 // we have been in a comment: forget what we read before
129                                                 bracketcount = 0;
130                                                 begin++;
131                                         }
132                                 }
133                                 break;
134                         case '{':
135                                 bracketcount++;
136                                 ignoreCloseBrackets = false;
137                                 break;
138                         case '}':
139                                 if (!ignoreCloseBrackets) {
140                                         bracketcount--;
141                                 }
142                                 break;
143                         case '"':
144                         case '\'':
145                                 begin = getStringEnd(document, begin, end, curr);
146                                 break;
147                         default:
148                         }
149                 }
150                 return bracketcount;
151         }
152
153         /**
154          * Returns the end position a comment starting at pos.
155          * 
156          * @returns the end position a comment starting at pos
157          * @param document -
158          *            the document being parsed
159          * @param position -
160          *            the start position for the search
161          * @param end -
162          *            the end position for the search
163          */
164         private int getCommentEnd(IDocument document, int position, int end)
165                         throws BadLocationException {
166                 int currentPosition = position;
167                 while (currentPosition < end) {
168                         char curr = document.getChar(currentPosition);
169                         currentPosition++;
170                         if (curr == '*') {
171                                 if (currentPosition < end
172                                                 && document.getChar(currentPosition) == '/') {
173                                         return currentPosition + 1;
174                                 }
175                         }
176                 }
177                 return end;
178         }
179
180         /**
181          * Returns the String at line with the leading whitespace removed.
182          * 
183          * @returns the String at line with the leading whitespace removed.
184          * @param document -
185          *            the document being parsed
186          * @param line -
187          *            the line being searched
188          */
189         protected String getIndentOfLine(IDocument document, int line)
190                         throws BadLocationException {
191                 if (line > -1) {
192                         int start = document.getLineOffset(line);
193                         int end = start + document.getLineLength(line) - 1;
194                         int whiteend = findEndOfWhiteSpace(document, start, end);
195                         return document.get(start, whiteend - start);
196                 } else {
197                         return ""; //$NON-NLS-1$
198                 }
199         }
200
201         /**
202          * Returns the position of the character in the document after position.
203          * 
204          * @returns the next location of character.
205          * @param document -
206          *            the document being parsed
207          * @param position -
208          *            the position to start searching from
209          * @param end -
210          *            the end of the document
211          * @param character -
212          *            the character you are trying to match
213          */
214         private int getStringEnd(IDocument document, int position, int end,
215                         char character) throws BadLocationException {
216                 int currentPosition = position;
217                 while (currentPosition < end) {
218                         char currentCharacter = document.getChar(currentPosition);
219                         currentPosition++;
220                         if (currentCharacter == '\\') {
221                                 // ignore escaped characters
222                                 currentPosition++;
223                         } else if (currentCharacter == character) {
224                                 return currentPosition;
225                         }
226                 }
227                 return end;
228         }
229
230         /**
231          * Set the indent of a new line based on the command provided in the
232          * supplied document.
233          * 
234          * @param document -
235          *            the document being parsed
236          * @param command -
237          *            the command being performed
238          */
239         protected void smartIndentAfterNewLine(IDocument document,
240                         DocumentCommand command) {
241
242                 int docLength = document.getLength();
243                 if (command.offset == -1 || docLength == 0)
244                         return;
245
246                 try {
247                         int p = (command.offset == docLength ? command.offset - 1
248                                         : command.offset);
249                         int line = document.getLineOfOffset(p);
250
251                         StringBuffer buf = new StringBuffer(command.text);
252                         if (command.offset < docLength
253                                         && document.getChar(command.offset) == '}') {
254                                 int indLine = findMatchingOpenBracket(document, line,
255                                                 command.offset, 0);
256                                 if (indLine == -1) {
257                                         indLine = line;
258                                 }
259                                 buf.append(getIndentOfLine(document, indLine));
260                         } else {
261                                 int start = document.getLineOffset(line);
262                                 int whiteend = findEndOfWhiteSpace(document, start,
263                                                 command.offset);
264                                 int offset = -1;
265                                 // if (command.offset > 0 && command.offset < docLength &&
266                                 // document.getChar(command.offset-1) == '{') {
267                                 // offset = command.offset;
268                                 // }
269                                 buf.append(document.get(start, whiteend - start));
270                                 if (getBracketCount(document, start, command.offset, true) > 0) {
271                                         buf.append('\t');
272                                 }
273                                 // if (offset >= 0) {
274                                 // buf.append('}');
275                                 // }
276                         }
277                         command.text = buf.toString();
278
279                 } catch (BadLocationException excp) {
280                         System.out.println(PHPEditorMessages
281                                         .getString("AutoIndent.error.bad_location_1")); //$NON-NLS-1$
282                 }
283         }
284
285         /**
286          * Set the indent of a bracket based on the command provided in the supplied
287          * document.
288          * 
289          * @param document -
290          *            the document being parsed
291          * @param command -
292          *            the command being performed
293          */
294         protected void smartInsertAfterBracket(IDocument document,
295                         DocumentCommand command) {
296                 if (command.offset == -1 || document.getLength() == 0)
297                         return;
298
299                 try {
300                         int p = (command.offset == document.getLength() ? command.offset - 1
301                                         : command.offset);
302                         int line = document.getLineOfOffset(p);
303                         int start = document.getLineOffset(line);
304                         int whiteend = findEndOfWhiteSpace(document, start, command.offset);
305
306                         // shift only when line does not contain any text up to the closing
307                         // bracket
308                         if (whiteend == command.offset) {
309                                 // evaluate the line with the opening bracket that matches out
310                                 // closing bracket
311                                 int indLine = findMatchingOpenBracket(document, line,
312                                                 command.offset, 1);
313                                 if (indLine != -1 && indLine != line) {
314                                         // take the indent of the found line
315                                         StringBuffer replaceText = new StringBuffer(
316                                                         getIndentOfLine(document, indLine));
317                                         // add the rest of the current line including the just added
318                                         // close bracket
319                                         replaceText.append(document.get(whiteend, command.offset
320                                                         - whiteend));
321                                         replaceText.append(command.text);
322                                         // modify document command
323                                         command.length = command.offset - start;
324                                         command.offset = start;
325                                         command.text = replaceText.toString();
326                                 }
327                         }
328                 } catch (BadLocationException excp) {
329                         System.out.println(PHPEditorMessages
330                                         .getString("AutoIndent.error.bad_location_2")); //$NON-NLS-1$
331                 }
332         }
333 }