Simple auto indent strategy when inserting templates
[phpeclipse.git] / net.sourceforge.phpeclipse / src / net / sourceforge / phpdt / internal / corext / template / php / JavaFormatter.java
diff --git a/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/corext/template/php/JavaFormatter.java b/net.sourceforge.phpeclipse/src/net/sourceforge/phpdt/internal/corext/template/php/JavaFormatter.java
new file mode 100644 (file)
index 0000000..5a9666d
--- /dev/null
@@ -0,0 +1,358 @@
+/*******************************************************************************
+ * Copyright (c) 2000, 2004 IBM Corporation and others.
+ * All rights reserved. This program and the accompanying materials 
+ * are made available under the terms of the Common Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/cpl-v10.html
+ * 
+ * Contributors:
+ *     IBM Corporation - initial API and implementation
+ *******************************************************************************/
+package net.sourceforge.phpdt.internal.corext.template.php;
+
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+
+import net.sourceforge.phpdt.core.JavaCore;
+import net.sourceforge.phpdt.internal.corext.util.CodeFormatterUtil;
+import net.sourceforge.phpdt.internal.corext.util.Strings;
+import net.sourceforge.phpdt.internal.formatter.CodeFormatter;
+import net.sourceforge.phpdt.internal.ui.text.IPHPPartitions;
+import net.sourceforge.phpdt.internal.ui.text.JavaHeuristicScanner;
+import net.sourceforge.phpdt.internal.ui.text.JavaIndenter;
+import net.sourceforge.phpeclipse.PHPeclipsePlugin;
+
+import org.eclipse.jface.text.BadLocationException;
+import org.eclipse.jface.text.Document;
+import org.eclipse.jface.text.IDocument;
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.ITypedRegion;
+import org.eclipse.jface.text.templates.DocumentTemplateContext;
+import org.eclipse.jface.text.templates.GlobalTemplateVariables;
+import org.eclipse.jface.text.templates.TemplateBuffer;
+import org.eclipse.jface.text.templates.TemplateContext;
+import org.eclipse.jface.text.templates.TemplateVariable;
+import org.eclipse.text.edits.DeleteEdit;
+import org.eclipse.text.edits.InsertEdit;
+import org.eclipse.text.edits.MalformedTreeException;
+import org.eclipse.text.edits.MultiTextEdit;
+import org.eclipse.text.edits.RangeMarker;
+import org.eclipse.text.edits.ReplaceEdit;
+import org.eclipse.text.edits.TextEdit;
+
+/**
+ * A template editor using the Java formatter to format a template buffer.
+ */
+public class JavaFormatter {
+
+  private static final String MARKER = "/*${" + GlobalTemplateVariables.Cursor.NAME + "}*/"; //$NON-NLS-1$ //$NON-NLS-2$
+
+  /** The line delimiter to use if code formatter is not used. */
+  private final String fLineDelimiter;
+
+  /** The initial indent level */
+  private final int fInitialIndentLevel;
+
+  /** The java partitioner */
+  private boolean fUseCodeFormatter;
+
+  /**
+   * Creates a JavaFormatter with the target line delimiter.
+   * 
+   * @param lineDelimiter
+   *          the line delimiter to use
+   * @param initialIndentLevel
+   *          the initial indentation level
+   * @param useCodeFormatter
+   *          <code>true</code> if the core code formatter should be used
+   */
+  public JavaFormatter(String lineDelimiter, int initialIndentLevel, boolean useCodeFormatter) {
+    fLineDelimiter = lineDelimiter;
+    fUseCodeFormatter = useCodeFormatter;
+    fInitialIndentLevel = initialIndentLevel;
+  }
+
+  /**
+   * Formats the template buffer.
+   * 
+   * @param buffer
+   * @param context
+   * @throws BadLocationException
+   */
+  public void format(TemplateBuffer buffer, TemplateContext context) throws BadLocationException {
+    try {
+      if (fUseCodeFormatter)
+        // try to format and fall back to indenting
+        try {
+          format(buffer, (JavaContext) context);
+        } catch (BadLocationException e) {
+          indent(buffer);
+        } catch (MalformedTreeException e) {
+          indent(buffer);
+        }
+      else
+        indent(buffer);
+
+      // don't trim the buffer if the replacement area is empty
+      // case: surrounding empty lines with block
+      if (context instanceof DocumentTemplateContext) {
+        DocumentTemplateContext dtc = (DocumentTemplateContext) context;
+        if (dtc.getStart() == dtc.getCompletionOffset())
+          if (dtc.getDocument().get(dtc.getStart(), dtc.getEnd() - dtc.getEnd()).trim().length() == 0)
+            return;
+      }
+
+      trimBegin(buffer);
+    } catch (MalformedTreeException e) {
+      throw new BadLocationException();
+    }
+  }
+
+  private static int getCaretOffset(TemplateVariable[] variables) {
+    for (int i = 0; i != variables.length; i++) {
+      TemplateVariable variable = variables[i];
+
+      if (variable.getType().equals(GlobalTemplateVariables.Cursor.NAME))
+        return variable.getOffsets()[0];
+    }
+
+    return -1;
+  }
+
+  private boolean isInsideCommentOrString(String string, int offset) {
+
+    IDocument document = new Document(string);
+    PHPeclipsePlugin.getDefault().getJavaTextTools().setupJavaDocumentPartitioner(document);
+
+    try {
+      ITypedRegion partition = document.getPartition(offset);
+      String partitionType = partition.getType();
+
+      return partitionType != null
+          && (partitionType.equals(IPHPPartitions.PHP_MULTILINE_COMMENT)
+              || partitionType.equals(IPHPPartitions.PHP_SINGLELINE_COMMENT) || partitionType.equals(IPHPPartitions.PHP_STRING_DQ)
+              || partitionType.equals(IPHPPartitions.PHP_STRING_SQ) || partitionType.equals(IPHPPartitions.PHP_PHPDOC_COMMENT));
+
+    } catch (BadLocationException e) {
+      return false;
+    }
+  }
+
+  private void format(TemplateBuffer templateBuffer, JavaContext context) throws BadLocationException {
+    // XXX 4360, 15247
+    // workaround for code formatter limitations
+    // handle a special case where cursor position is surrounded by whitespaces
+
+    String string = templateBuffer.getString();
+    TemplateVariable[] variables = templateBuffer.getVariables();
+
+    int caretOffset = getCaretOffset(variables);
+    if ((caretOffset > 0) && Character.isWhitespace(string.charAt(caretOffset - 1)) && (caretOffset < string.length())
+        && Character.isWhitespace(string.charAt(caretOffset)) && !isInsideCommentOrString(string, caretOffset)) {
+      List positions = variablesToPositions(variables);
+
+      TextEdit insert = new InsertEdit(caretOffset, MARKER);
+      string = edit(string, positions, insert);
+      positionsToVariables(positions, variables);
+      templateBuffer.setContent(string, variables);
+
+      plainFormat(templateBuffer, context);
+
+      string = templateBuffer.getString();
+      variables = templateBuffer.getVariables();
+      caretOffset = getCaretOffset(variables);
+
+      positions = variablesToPositions(variables);
+      TextEdit delete = new DeleteEdit(caretOffset, MARKER.length());
+      string = edit(string, positions, delete);
+      positionsToVariables(positions, variables);
+      templateBuffer.setContent(string, variables);
+
+    } else {
+      plainFormat(templateBuffer, context);
+    }
+  }
+
+  private void plainFormat(TemplateBuffer templateBuffer, JavaContext context) throws BadLocationException {
+  }
+
+  //   private void plainFormat(TemplateBuffer templateBuffer, JavaContext context) throws BadLocationException {
+  //           
+  //           IDocument doc= new Document(templateBuffer.getString());
+  //           
+  //           TemplateVariable[] variables= templateBuffer.getVariables();
+  //           
+  //           List offsets= variablesToPositions(variables);
+  //           
+  //           Map options;
+  //           if (context.getCompilationUnit() != null)
+  //                   options= context.getCompilationUnit().getJavaProject().getOptions(true);
+  //           else
+  //                   options= JavaCore.getOptions();
+  //           
+  //           TextEdit edit= CodeFormatterUtil.format2(CodeFormatter.K_UNKNOWN, doc.get(), fInitialIndentLevel, fLineDelimiter, options);
+  //           if (edit == null)
+  //                   throw new BadLocationException(); // fall back to indenting
+  //           
+  //           MultiTextEdit root;
+  //           if (edit instanceof MultiTextEdit)
+  //                   root= (MultiTextEdit) edit;
+  //           else {
+  //                   root= new MultiTextEdit(0, doc.getLength());
+  //                   root.addChild(edit);
+  //           }
+  //           for (Iterator it= offsets.iterator(); it.hasNext();) {
+  //                   TextEdit position= (TextEdit) it.next();
+  //                   try {
+  //                           root.addChild(position);
+  //                   } catch (MalformedTreeException e) {
+  //                           // position conflicts with formatter edit
+  //                           // ignore this position
+  //                   }
+  //           }
+  //           
+  //           root.apply(doc, TextEdit.UPDATE_REGIONS);
+  //           
+  //           positionsToVariables(offsets, variables);
+  //           
+  //           templateBuffer.setContent(doc.get(), variables);
+  //   }
+
+  private void indent(TemplateBuffer templateBuffer) throws BadLocationException, MalformedTreeException {
+
+    TemplateVariable[] variables = templateBuffer.getVariables();
+    List positions = variablesToPositions(variables);
+
+    IDocument document = new Document(templateBuffer.getString());
+    MultiTextEdit root = new MultiTextEdit(0, document.getLength());
+    root.addChildren((TextEdit[]) positions.toArray(new TextEdit[positions.size()]));
+
+    JavaHeuristicScanner scanner = new JavaHeuristicScanner(document);
+    JavaIndenter indenter = new JavaIndenter(document, scanner);
+
+    // first line
+    int offset = document.getLineOffset(0);
+    TextEdit edit = new InsertEdit(offset, CodeFormatterUtil.createIndentString(fInitialIndentLevel));
+    root.addChild(edit);
+    root.apply(document, TextEdit.UPDATE_REGIONS);
+    root.removeChild(edit);
+
+    formatDelimiter(document, root, 0);
+
+    // following lines
+    int lineCount = document.getNumberOfLines();
+
+    for (int line = 1; line < lineCount; line++) {
+      IRegion region = document.getLineInformation(line);
+      offset = region.getOffset();
+      StringBuffer indent = indenter.computeIndentation(offset);
+      if (indent == null)
+        continue;
+  // axelcl delete start
+//      int nonWS = scanner.findNonWhitespaceForwardInAnyPartition(offset, offset + region.getLength());
+//      if (nonWS == JavaHeuristicScanner.NOT_FOUND)
+//        continue;
+//      edit = new ReplaceEdit(offset, nonWS - offset, indent.toString());
+//    axelcl delete end
+//    axelcl insert start
+      int nonWS = offset;
+      edit = new ReplaceEdit(offset, nonWS - offset, CodeFormatterUtil.createIndentString(fInitialIndentLevel));
+   // axelcl insert end
+      root.addChild(edit);
+      root.apply(document, TextEdit.UPDATE_REGIONS);
+      root.removeChild(edit);
+
+      formatDelimiter(document, root, line);
+    }
+
+    positionsToVariables(positions, variables);
+    templateBuffer.setContent(document.get(), variables);
+  }
+
+  /**
+   * Changes the delimiter to the configured line delimiter.
+   * 
+   * @param document
+   *          the temporary document being edited
+   * @param root
+   *          the root edit containing all positions that will be updated along the way
+   * @param line
+   *          the line to format
+   * @throws BadLocationException
+   *           if applying the changes fails
+   */
+  private void formatDelimiter(IDocument document, MultiTextEdit root, int line) throws BadLocationException {
+    IRegion region = document.getLineInformation(line);
+    String lineDelimiter = document.getLineDelimiter(line);
+    if (lineDelimiter != null) {
+      TextEdit edit = new ReplaceEdit(region.getOffset() + region.getLength(), lineDelimiter.length(), fLineDelimiter);
+      root.addChild(edit);
+      root.apply(document, TextEdit.UPDATE_REGIONS);
+      root.removeChild(edit);
+    }
+  }
+
+  private static void trimBegin(TemplateBuffer templateBuffer) throws BadLocationException {
+    String string = templateBuffer.getString();
+    TemplateVariable[] variables = templateBuffer.getVariables();
+
+    List positions = variablesToPositions(variables);
+
+    int i = 0;
+    while ((i != string.length()) && Character.isWhitespace(string.charAt(i)))
+      i++;
+
+    string = edit(string, positions, new DeleteEdit(0, i));
+    positionsToVariables(positions, variables);
+
+    templateBuffer.setContent(string, variables);
+  }
+
+  private static String edit(String string, List positions, TextEdit edit) throws BadLocationException {
+    MultiTextEdit root = new MultiTextEdit(0, string.length());
+    root.addChildren((TextEdit[]) positions.toArray(new TextEdit[positions.size()]));
+    root.addChild(edit);
+    IDocument document = new Document(string);
+    root.apply(document);
+
+    return document.get();
+  }
+
+  private static List variablesToPositions(TemplateVariable[] variables) {
+    List positions = new ArrayList(5);
+    for (int i = 0; i != variables.length; i++) {
+      int[] offsets = variables[i].getOffsets();
+
+      // trim positions off whitespace
+      String value = variables[i].getDefaultValue();
+      int wsStart = 0;
+      while (wsStart < value.length() && Character.isWhitespace(value.charAt(wsStart))
+          && !Strings.isLineDelimiterChar(value.charAt(wsStart)))
+        wsStart++;
+
+      variables[i].getValues()[0] = value.substring(wsStart);
+
+      for (int j = 0; j != offsets.length; j++) {
+        offsets[j] += wsStart;
+        positions.add(new RangeMarker(offsets[j], 0));
+      }
+    }
+    return positions;
+  }
+
+  private static void positionsToVariables(List positions, TemplateVariable[] variables) {
+    Iterator iterator = positions.iterator();
+
+    for (int i = 0; i != variables.length; i++) {
+      TemplateVariable variable = variables[i];
+
+      int[] offsets = new int[variable.getOffsets().length];
+      for (int j = 0; j != offsets.length; j++)
+        offsets[j] = ((TextEdit) iterator.next()).getOffset();
+
+      variable.setOffsets(offsets);
+    }
+  }
+}
\ No newline at end of file