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
 
   9  *     IBM Corporation - initial API and implementation
 
  10  *******************************************************************************/
 
  11 package net.sourceforge.phpdt.internal.corext.template.php;
 
  13 import java.util.ArrayList;
 
  14 import java.util.Iterator;
 
  15 import java.util.List;
 
  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;
 
  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;
 
  44  * A template editor using the Java formatter to format a template buffer.
 
  46 public class JavaFormatter {
 
  48         private static final String MARKER = "/*${" + GlobalTemplateVariables.Cursor.NAME + "}*/"; //$NON-NLS-1$ //$NON-NLS-2$
 
  50         /** The line delimiter to use if code formatter is not used. */
 
  51         private final String fLineDelimiter;
 
  53         /** The initial indent level */
 
  54         private final int fInitialIndentLevel;
 
  56         /** The java partitioner */
 
  57         private boolean fUseCodeFormatter;
 
  60          * Creates a JavaFormatter with the target line delimiter.
 
  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
 
  69         public JavaFormatter(String lineDelimiter, int initialIndentLevel,
 
  70                         boolean useCodeFormatter) {
 
  71                 fLineDelimiter = lineDelimiter;
 
  72                 fUseCodeFormatter = useCodeFormatter;
 
  73                 fInitialIndentLevel = initialIndentLevel;
 
  77          * Formats the template buffer.
 
  81          * @throws BadLocationException
 
  83         public void format(TemplateBuffer buffer, TemplateContext context)
 
  84                         throws BadLocationException {
 
  86                         if (fUseCodeFormatter)
 
  87                                 // try to format and fall back to indenting
 
  89                                         format(buffer, (JavaContext) context);
 
  90                                 } catch (BadLocationException e) {
 
  92                                 } catch (MalformedTreeException e) {
 
  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)
 
 109                 } catch (MalformedTreeException e) {
 
 110                         throw new BadLocationException();
 
 114         private static int getCaretOffset(TemplateVariable[] variables) {
 
 115                 for (int i = 0; i != variables.length; i++) {
 
 116                         TemplateVariable variable = variables[i];
 
 118                         if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
 
 119                                 return variable.getOffsets()[0];
 
 125         private boolean isInsideCommentOrString(String string, int offset) {
 
 127                 IDocument document = new Document(string);
 
 128                 WebUI.getDefault().getJavaTextTools()
 
 129                                 .setupJavaDocumentPartitioner(document);
 
 132                         ITypedRegion partition = document.getPartition(offset);
 
 133                         String partitionType = partition.getType();
 
 135                         return partitionType != null
 
 137                                                         .equals(IPHPPartitions.PHP_MULTILINE_COMMENT)
 
 139                                                                         .equals(IPHPPartitions.PHP_SINGLELINE_COMMENT)
 
 141                                                                         .equals(IPHPPartitions.PHP_STRING_DQ)
 
 143                                                                         .equals(IPHPPartitions.PHP_STRING_SQ)
 
 145                                                                         .equals(IPHPPartitions.PHP_STRING_HEREDOC) || partitionType
 
 146                                                         .equals(IPHPPartitions.PHP_PHPDOC_COMMENT));
 
 148                 } catch (BadLocationException e) {
 
 153         private void format(TemplateBuffer templateBuffer, JavaContext context)
 
 154                         throws BadLocationException {
 
 156                 // workaround for code formatter limitations
 
 157                 // handle a special case where cursor position is surrounded by
 
 160                 String string = templateBuffer.getString();
 
 161                 TemplateVariable[] variables = templateBuffer.getVariables();
 
 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);
 
 171                         TextEdit insert = new InsertEdit(caretOffset, MARKER);
 
 172                         string = edit(string, positions, insert);
 
 173                         positionsToVariables(positions, variables);
 
 174                         templateBuffer.setContent(string, variables);
 
 176                         plainFormat(templateBuffer, context);
 
 178                         string = templateBuffer.getString();
 
 179                         variables = templateBuffer.getVariables();
 
 180                         caretOffset = getCaretOffset(variables);
 
 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);
 
 189                         plainFormat(templateBuffer, context);
 
 193         private void plainFormat(TemplateBuffer templateBuffer, JavaContext context)
 
 194                         throws BadLocationException {
 
 197         // private void plainFormat(TemplateBuffer templateBuffer, JavaContext
 
 198         // context) throws BadLocationException {
 
 200         // IDocument doc= new Document(templateBuffer.getString());
 
 202         // TemplateVariable[] variables= templateBuffer.getVariables();
 
 204         // List offsets= variablesToPositions(variables);
 
 207         // if (context.getCompilationUnit() != null)
 
 208         // options= context.getCompilationUnit().getJavaProject().getOptions(true);
 
 210         // options= JavaCore.getOptions();
 
 212         // TextEdit edit= CodeFormatterUtil.format2(CodeFormatter.K_UNKNOWN,
 
 213         // doc.get(), fInitialIndentLevel, fLineDelimiter, options);
 
 215         // throw new BadLocationException(); // fall back to indenting
 
 217         // MultiTextEdit root;
 
 218         // if (edit instanceof MultiTextEdit)
 
 219         // root= (MultiTextEdit) edit;
 
 221         // root= new MultiTextEdit(0, doc.getLength());
 
 222         // root.addChild(edit);
 
 224         // for (Iterator it= offsets.iterator(); it.hasNext();) {
 
 225         // TextEdit position= (TextEdit) it.next();
 
 227         // root.addChild(position);
 
 228         // } catch (MalformedTreeException e) {
 
 229         // // position conflicts with formatter edit
 
 230         // // ignore this position
 
 234         // root.apply(doc, TextEdit.UPDATE_REGIONS);
 
 236         // positionsToVariables(offsets, variables);
 
 238         // templateBuffer.setContent(doc.get(), variables);
 
 241         private void indent(TemplateBuffer templateBuffer)
 
 242                         throws BadLocationException, MalformedTreeException {
 
 244                 TemplateVariable[] variables = templateBuffer.getVariables();
 
 245                 List positions = variablesToPositions(variables);
 
 247                 IDocument document = new Document(templateBuffer.getString());
 
 248                 MultiTextEdit root = new MultiTextEdit(0, document.getLength());
 
 249                 root.addChildren((TextEdit[]) positions.toArray(new TextEdit[positions
 
 252                 JavaHeuristicScanner scanner = new JavaHeuristicScanner(document);
 
 253                 JavaIndenter indenter = new JavaIndenter(document, scanner);
 
 256                 int offset = document.getLineOffset(0);
 
 257                 TextEdit edit = new InsertEdit(offset, CodeFormatterUtil
 
 258                                 .createIndentString(fInitialIndentLevel));
 
 260                 root.apply(document, TextEdit.UPDATE_REGIONS);
 
 261                 root.removeChild(edit);
 
 263                 formatDelimiter(document, root, 0);
 
 266                 int lineCount = document.getNumberOfLines();
 
 268                 for (int line = 1; line < lineCount; line++) {
 
 269                         IRegion region = document.getLineInformation(line);
 
 270                         offset = region.getOffset();
 
 271                         StringBuffer indent = indenter.computeIndentation(offset);
 
 274                         // axelcl delete start
 
 276                         // scanner.findNonWhitespaceForwardInAnyPartition(offset, offset +
 
 277                         // region.getLength());
 
 278                         // if (nonWS == JavaHeuristicScanner.NOT_FOUND)
 
 280                         // edit = new ReplaceEdit(offset, nonWS - offset,
 
 281                         // indent.toString());
 
 283                         // axelcl insert start
 
 285                         edit = new ReplaceEdit(offset, nonWS - offset, CodeFormatterUtil
 
 286                                         .createIndentString(fInitialIndentLevel));
 
 289                         root.apply(document, TextEdit.UPDATE_REGIONS);
 
 290                         root.removeChild(edit);
 
 292                         formatDelimiter(document, root, line);
 
 295                 positionsToVariables(positions, variables);
 
 296                 templateBuffer.setContent(document.get(), variables);
 
 300          * Changes the delimiter to the configured line delimiter.
 
 303          *            the temporary document being edited
 
 305          *            the root edit containing all positions that will be updated
 
 309          * @throws BadLocationException
 
 310          *             if applying the changes fails
 
 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(),
 
 321                         root.apply(document, TextEdit.UPDATE_REGIONS);
 
 322                         root.removeChild(edit);
 
 326         private static void trimBegin(TemplateBuffer templateBuffer)
 
 327                         throws BadLocationException {
 
 328                 String string = templateBuffer.getString();
 
 329                 TemplateVariable[] variables = templateBuffer.getVariables();
 
 331                 List positions = variablesToPositions(variables);
 
 334                 while ((i != string.length())
 
 335                                 && Character.isWhitespace(string.charAt(i)))
 
 338                 string = edit(string, positions, new DeleteEdit(0, i));
 
 339                 positionsToVariables(positions, variables);
 
 341                 templateBuffer.setContent(string, variables);
 
 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
 
 350                 IDocument document = new Document(string);
 
 351                 root.apply(document);
 
 353                 return document.get();
 
 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();
 
 361                         // trim positions off whitespace
 
 362                         String value = variables[i].getDefaultValue();
 
 364                         while (wsStart < value.length()
 
 365                                         && Character.isWhitespace(value.charAt(wsStart))
 
 366                                         && !Strings.isLineDelimiterChar(value.charAt(wsStart)))
 
 369                         variables[i].getValues()[0] = value.substring(wsStart);
 
 371                         for (int j = 0; j != offsets.length; j++) {
 
 372                                 offsets[j] += wsStart;
 
 373                                 positions.add(new RangeMarker(offsets[j], 0));
 
 379         private static void positionsToVariables(List positions,
 
 380                         TemplateVariable[] variables) {
 
 381                 Iterator iterator = positions.iterator();
 
 383                 for (int i = 0; i != variables.length; i++) {
 
 384                         TemplateVariable variable = variables[i];
 
 386                         int[] offsets = new int[variable.getOffsets().length];
 
 387                         for (int j = 0; j != offsets.length; j++)
 
 388                                 offsets[j] = ((TextEdit) iterator.next()).getOffset();
 
 390                         variable.setOffsets(offsets);