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