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