1c815b7cfc19c8006054518cdc171a04065c620b
[phpeclipse.git] / net.sourceforge.phpeclipse.ui / src / net / sourceforge / phpdt / internal / corext / template / php / JavaFormatter.java
1 /*******************************************************************************
2  * Copyright (c) 2000, 2004 IBM Corporation 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 API and implementation
10  *******************************************************************************/
11 package net.sourceforge.phpdt.internal.corext.template.php;
12
13 import java.util.ArrayList;
14 import java.util.Iterator;
15 import java.util.List;
16
17 import net.sourceforge.phpdt.internal.corext.util.CodeFormatterUtil;
18 import net.sourceforge.phpdt.internal.corext.util.Strings;
19 import net.sourceforge.phpdt.internal.ui.text.IPHPPartitions;
20 import net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner;
21 import net.sourceforge.phpdt.internal.ui.text.JavaIndenter;
22 import net.sourceforge.phpeclipse.PHPeclipsePlugin;
23 import net.sourceforge.phpeclipse.ui.WebUI;
24
25 import org.eclipse.jface.text.BadLocationException;
26 import org.eclipse.jface.text.Document;
27 import org.eclipse.jface.text.IDocument;
28 import org.eclipse.jface.text.IRegion;
29 import org.eclipse.jface.text.ITypedRegion;
30 import org.eclipse.jface.text.templates.DocumentTemplateContext;
31 import org.eclipse.jface.text.templates.GlobalTemplateVariables;
32 import org.eclipse.jface.text.templates.TemplateBuffer;
33 import org.eclipse.jface.text.templates.TemplateContext;
34 import org.eclipse.jface.text.templates.TemplateVariable;
35 import org.eclipse.text.edits.DeleteEdit;
36 import org.eclipse.text.edits.InsertEdit;
37 import org.eclipse.text.edits.MalformedTreeException;
38 import org.eclipse.text.edits.MultiTextEdit;
39 import org.eclipse.text.edits.RangeMarker;
40 import org.eclipse.text.edits.ReplaceEdit;
41 import org.eclipse.text.edits.TextEdit;
42
43 /**
44  * A template editor using the Java formatter to format a template buffer.
45  */
46 public class JavaFormatter {
47
48         private static final String MARKER = "/*${" + GlobalTemplateVariables.Cursor.NAME + "}*/"; //$NON-NLS-1$ //$NON-NLS-2$
49
50         /** The line delimiter to use if code formatter is not used. */
51         private final String fLineDelimiter;
52
53         /** The initial indent level */
54         private final int fInitialIndentLevel;
55
56         /** The java partitioner */
57         private boolean fUseCodeFormatter;
58
59         /**
60          * Creates a JavaFormatter with the target line delimiter.
61          * 
62          * @param lineDelimiter
63          *            the line delimiter to use
64          * @param initialIndentLevel
65          *            the initial indentation level
66          * @param useCodeFormatter
67          *            <code>true</code> if the core code formatter should be used
68          */
69         public JavaFormatter(String lineDelimiter, int initialIndentLevel,
70                         boolean useCodeFormatter) {
71                 fLineDelimiter = lineDelimiter;
72                 fUseCodeFormatter = useCodeFormatter;
73                 fInitialIndentLevel = initialIndentLevel;
74         }
75
76         /**
77          * Formats the template buffer.
78          * 
79          * @param buffer
80          * @param context
81          * @throws BadLocationException
82          */
83         public void format(TemplateBuffer buffer, TemplateContext context)
84                         throws BadLocationException {
85                 try {
86                         if (fUseCodeFormatter)
87                                 // try to format and fall back to indenting
88                                 try {
89                                         format(buffer, (JavaContext) context);
90                                 } catch (BadLocationException e) {
91                                         indent(buffer);
92                                 } catch (MalformedTreeException e) {
93                                         indent(buffer);
94                                 }
95                         else
96                                 indent(buffer);
97
98                         // don't trim the buffer if the replacement area is empty
99                         // case: surrounding empty lines with block
100                         if (context instanceof DocumentTemplateContext) {
101                                 DocumentTemplateContext dtc = (DocumentTemplateContext) context;
102                                 if (dtc.getStart() == dtc.getCompletionOffset())
103                                         if (dtc.getDocument().get(dtc.getStart(),
104                                                         dtc.getEnd() - dtc.getEnd()).trim().length() == 0)
105                                                 return;
106                         }
107
108                         trimBegin(buffer);
109                 } catch (MalformedTreeException e) {
110                         throw new BadLocationException();
111                 }
112         }
113
114         private static int getCaretOffset(TemplateVariable[] variables) {
115                 for (int i = 0; i != variables.length; i++) {
116                         TemplateVariable variable = variables[i];
117
118                         if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
119                                 return variable.getOffsets()[0];
120                 }
121
122                 return -1;
123         }
124
125         private boolean isInsideCommentOrString(String string, int offset) {
126
127                 IDocument document = new Document(string);
128                 WebUI.getDefault().getJavaTextTools()
129                                 .setupJavaDocumentPartitioner(document);
130
131                 try {
132                         ITypedRegion partition = document.getPartition(offset);
133                         String partitionType = partition.getType();
134
135                         return partitionType != null
136                                         && (partitionType
137                                                         .equals(IPHPPartitions.PHP_MULTILINE_COMMENT)
138                                                         || partitionType
139                                                                         .equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)
140                                                         || partitionType
141                                                                         .equals(IPHPPartitions.PHP_STRING_DQ)
142                                                         || partitionType
143                                                                         .equals(IPHPPartitions.PHP_STRING_SQ)
144                                                         || partitionType
145                                                                         .equals(IPHPPartitions.PHP_STRING_HEREDOC) || partitionType
146                                                         .equals(IPHPPartitions.PHP_PHPDOC_COMMENT));
147
148                 } catch (BadLocationException e) {
149                         return false;
150                 }
151         }
152
153         private void format(TemplateBuffer templateBuffer, JavaContext context)
154                         throws BadLocationException {
155                 // XXX 4360, 15247
156                 // workaround for code formatter limitations
157                 // handle a special case where cursor position is surrounded by
158                 // whitespaces
159
160                 String string = templateBuffer.getString();
161                 TemplateVariable[] variables = templateBuffer.getVariables();
162
163                 int caretOffset = getCaretOffset(variables);
164                 if ((caretOffset > 0)
165                                 && Character.isWhitespace(string.charAt(caretOffset - 1))
166                                 && (caretOffset < string.length())
167                                 && Character.isWhitespace(string.charAt(caretOffset))
168                                 && !isInsideCommentOrString(string, caretOffset)) {
169                         List positions = variablesToPositions(variables);
170
171                         TextEdit insert = new InsertEdit(caretOffset, MARKER);
172                         string = edit(string, positions, insert);
173                         positionsToVariables(positions, variables);
174                         templateBuffer.setContent(string, variables);
175
176                         plainFormat(templateBuffer, context);
177
178                         string = templateBuffer.getString();
179                         variables = templateBuffer.getVariables();
180                         caretOffset = getCaretOffset(variables);
181
182                         positions = variablesToPositions(variables);
183                         TextEdit delete = new DeleteEdit(caretOffset, MARKER.length());
184                         string = edit(string, positions, delete);
185                         positionsToVariables(positions, variables);
186                         templateBuffer.setContent(string, variables);
187
188                 } else {
189                         plainFormat(templateBuffer, context);
190                 }
191         }
192
193         private void plainFormat(TemplateBuffer templateBuffer, JavaContext context)
194                         throws BadLocationException {
195         }
196
197         // private void plainFormat(TemplateBuffer templateBuffer, JavaContext
198         // context) throws BadLocationException {
199         //
200         // IDocument doc= new Document(templateBuffer.getString());
201         //
202         // TemplateVariable[] variables= templateBuffer.getVariables();
203         //
204         // List offsets= variablesToPositions(variables);
205         //
206         // Map options;
207         // if (context.getCompilationUnit() != null)
208         // options= context.getCompilationUnit().getJavaProject().getOptions(true);
209         // else
210         // options= JavaCore.getOptions();
211         //
212         // TextEdit edit= CodeFormatterUtil.format2(CodeFormatter.K_UNKNOWN,
213         // doc.get(), fInitialIndentLevel, fLineDelimiter, options);
214         // if (edit == null)
215         // throw new BadLocationException(); // fall back to indenting
216         //
217         // MultiTextEdit root;
218         // if (edit instanceof MultiTextEdit)
219         // root= (MultiTextEdit) edit;
220         // else {
221         // root= new MultiTextEdit(0, doc.getLength());
222         // root.addChild(edit);
223         // }
224         // for (Iterator it= offsets.iterator(); it.hasNext();) {
225         // TextEdit position= (TextEdit) it.next();
226         // try {
227         // root.addChild(position);
228         // } catch (MalformedTreeException e) {
229         // // position conflicts with formatter edit
230         // // ignore this position
231         // }
232         // }
233         //
234         // root.apply(doc, TextEdit.UPDATE_REGIONS);
235         //
236         // positionsToVariables(offsets, variables);
237         //
238         // templateBuffer.setContent(doc.get(), variables);
239         // }
240
241         private void indent(TemplateBuffer templateBuffer)
242                         throws BadLocationException, MalformedTreeException {
243
244                 TemplateVariable[] variables = templateBuffer.getVariables();
245                 List positions = variablesToPositions(variables);
246
247                 IDocument document = new Document(templateBuffer.getString());
248                 MultiTextEdit root = new MultiTextEdit(0, document.getLength());
249                 root.addChildren((TextEdit[]) positions.toArray(new TextEdit[positions
250                                 .size()]));
251
252                 JavaHeuristicScanner scanner = new JavaHeuristicScanner(document);
253                 JavaIndenter indenter = new JavaIndenter(document, scanner);
254
255                 // first line
256                 int offset = document.getLineOffset(0);
257                 TextEdit edit = new InsertEdit(offset, CodeFormatterUtil
258                                 .createIndentString(fInitialIndentLevel));
259                 root.addChild(edit);
260                 root.apply(document, TextEdit.UPDATE_REGIONS);
261                 root.removeChild(edit);
262
263                 formatDelimiter(document, root, 0);
264
265                 // following lines
266                 int lineCount = document.getNumberOfLines();
267
268                 for (int line = 1; line < lineCount; line++) {
269                         IRegion region = document.getLineInformation(line);
270                         offset = region.getOffset();
271                         StringBuffer indent = indenter.computeIndentation(offset);
272                         if (indent == null)
273                                 continue;
274                         // axelcl delete start
275                         // int nonWS =
276                         // scanner.findNonWhitespaceForwardInAnyPartition(offset, offset +
277                         // region.getLength());
278                         // if (nonWS == JavaHeuristicScanner.NOT_FOUND)
279                         // continue;
280                         // edit = new ReplaceEdit(offset, nonWS - offset,
281                         // indent.toString());
282                         // axelcl delete end
283                         // axelcl insert start
284                         int nonWS = offset;
285                         edit = new ReplaceEdit(offset, nonWS - offset, CodeFormatterUtil
286                                         .createIndentString(fInitialIndentLevel));
287                         // axelcl insert end
288                         root.addChild(edit);
289                         root.apply(document, TextEdit.UPDATE_REGIONS);
290                         root.removeChild(edit);
291
292                         formatDelimiter(document, root, line);
293                 }
294
295                 positionsToVariables(positions, variables);
296                 templateBuffer.setContent(document.get(), variables);
297         }
298
299         /**
300          * Changes the delimiter to the configured line delimiter.
301          * 
302          * @param document
303          *            the temporary document being edited
304          * @param root
305          *            the root edit containing all positions that will be updated
306          *            along the way
307          * @param line
308          *            the line to format
309          * @throws BadLocationException
310          *             if applying the changes fails
311          */
312         private void formatDelimiter(IDocument document, MultiTextEdit root,
313                         int line) throws BadLocationException {
314                 IRegion region = document.getLineInformation(line);
315                 String lineDelimiter = document.getLineDelimiter(line);
316                 if (lineDelimiter != null) {
317                         TextEdit edit = new ReplaceEdit(region.getOffset()
318                                         + region.getLength(), lineDelimiter.length(),
319                                         fLineDelimiter);
320                         root.addChild(edit);
321                         root.apply(document, TextEdit.UPDATE_REGIONS);
322                         root.removeChild(edit);
323                 }
324         }
325
326         private static void trimBegin(TemplateBuffer templateBuffer)
327                         throws BadLocationException {
328                 String string = templateBuffer.getString();
329                 TemplateVariable[] variables = templateBuffer.getVariables();
330
331                 List positions = variablesToPositions(variables);
332
333                 int i = 0;
334                 while ((i != string.length())
335                                 && Character.isWhitespace(string.charAt(i)))
336                         i++;
337
338                 string = edit(string, positions, new DeleteEdit(0, i));
339                 positionsToVariables(positions, variables);
340
341                 templateBuffer.setContent(string, variables);
342         }
343
344         private static String edit(String string, List positions, TextEdit edit)
345                         throws BadLocationException {
346                 MultiTextEdit root = new MultiTextEdit(0, string.length());
347                 root.addChildren((TextEdit[]) positions.toArray(new TextEdit[positions
348                                 .size()]));
349                 root.addChild(edit);
350                 IDocument document = new Document(string);
351                 root.apply(document);
352
353                 return document.get();
354         }
355
356         private static List variablesToPositions(TemplateVariable[] variables) {
357                 List positions = new ArrayList(5);
358                 for (int i = 0; i != variables.length; i++) {
359                         int[] offsets = variables[i].getOffsets();
360
361                         // trim positions off whitespace
362                         String value = variables[i].getDefaultValue();
363                         int wsStart = 0;
364                         while (wsStart < value.length()
365                                         && Character.isWhitespace(value.charAt(wsStart))
366                                         && !Strings.isLineDelimiterChar(value.charAt(wsStart)))
367                                 wsStart++;
368
369                         variables[i].getValues()[0] = value.substring(wsStart);
370
371                         for (int j = 0; j != offsets.length; j++) {
372                                 offsets[j] += wsStart;
373                                 positions.add(new RangeMarker(offsets[j], 0));
374                         }
375                 }
376                 return positions;
377         }
378
379         private static void positionsToVariables(List positions,
380                         TemplateVariable[] variables) {
381                 Iterator iterator = positions.iterator();
382
383                 for (int i = 0; i != variables.length; i++) {
384                         TemplateVariable variable = variables[i];
385
386                         int[] offsets = new int[variable.getOffsets().length];
387                         for (int j = 0; j != offsets.length; j++)
388                                 offsets[j] = ((TextEdit) iterator.next()).getOffset();
389
390                         variable.setOffsets(offsets);
391                 }
392         }
393 }