reusable formatter classes
diff --git a/core/plugins/org.eclipse.dltk.formatter/.classpath b/core/plugins/org.eclipse.dltk.formatter/.classpath
new file mode 100644
index 0000000..2fbb7a2
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/.classpath
@@ -0,0 +1,7 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<classpath>
+	<classpathentry kind="con" path="org.eclipse.jdt.launching.JRE_CONTAINER/org.eclipse.jdt.internal.debug.ui.launcher.StandardVMType/J2SE-1.4"/>
+	<classpathentry kind="con" path="org.eclipse.pde.core.requiredPlugins"/>
+	<classpathentry kind="src" path="src"/>
+	<classpathentry kind="output" path="bin"/>
+</classpath>
diff --git a/core/plugins/org.eclipse.dltk.formatter/.project b/core/plugins/org.eclipse.dltk.formatter/.project
new file mode 100644
index 0000000..8a0451b
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/.project
@@ -0,0 +1,28 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<projectDescription>
+	<name>org.eclipse.dltk.formatter</name>
+	<comment></comment>
+	<projects>
+	</projects>
+	<buildSpec>
+		<buildCommand>
+			<name>org.eclipse.jdt.core.javabuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.ManifestBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+		<buildCommand>
+			<name>org.eclipse.pde.SchemaBuilder</name>
+			<arguments>
+			</arguments>
+		</buildCommand>
+	</buildSpec>
+	<natures>
+		<nature>org.eclipse.pde.PluginNature</nature>
+		<nature>org.eclipse.jdt.core.javanature</nature>
+	</natures>
+</projectDescription>
diff --git a/core/plugins/org.eclipse.dltk.formatter/.settings/org.eclipse.jdt.core.prefs b/core/plugins/org.eclipse.dltk.formatter/.settings/org.eclipse.jdt.core.prefs
new file mode 100644
index 0000000..701fe0c
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/.settings/org.eclipse.jdt.core.prefs
@@ -0,0 +1,12 @@
+#Mon Oct 06 19:13:06 NOVST 2008
+eclipse.preferences.version=1
+org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled
+org.eclipse.jdt.core.compiler.codegen.targetPlatform=1.4
+org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve
+org.eclipse.jdt.core.compiler.compliance=1.4
+org.eclipse.jdt.core.compiler.debug.lineNumber=generate
+org.eclipse.jdt.core.compiler.debug.localVariable=generate
+org.eclipse.jdt.core.compiler.debug.sourceFile=generate
+org.eclipse.jdt.core.compiler.problem.assertIdentifier=error
+org.eclipse.jdt.core.compiler.problem.enumIdentifier=warning
+org.eclipse.jdt.core.compiler.source=1.4
diff --git a/core/plugins/org.eclipse.dltk.formatter/META-INF/MANIFEST.MF b/core/plugins/org.eclipse.dltk.formatter/META-INF/MANIFEST.MF
new file mode 100644
index 0000000..c9c867b
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/META-INF/MANIFEST.MF
@@ -0,0 +1,13 @@
+Manifest-Version: 1.0
+Bundle-ManifestVersion: 2
+Bundle-Name: %pluginName
+Bundle-SymbolicName: org.eclipse.dltk.formatter
+Bundle-Version: 1.0.0.qualifier
+Bundle-RequiredExecutionEnvironment: J2SE-1.4
+Bundle-Vendor: %pluginProvider
+Bundle-Localization: plugin
+Export-Package: org.eclipse.dltk.formatter
+Require-Bundle: org.eclipse.core.runtime,
+ org.eclipse.jface.text,
+ org.eclipse.dltk.core,
+ org.eclipse.dltk.ui
diff --git a/core/plugins/org.eclipse.dltk.formatter/build.properties b/core/plugins/org.eclipse.dltk.formatter/build.properties
new file mode 100644
index 0000000..43d919b
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/build.properties
@@ -0,0 +1,7 @@
+javacSource=1.4
+javacTarget=1.4
+source.. = src/
+output.. = bin/
+bin.includes = META-INF/,\
+               .,\
+               plugin.properties
diff --git a/core/plugins/org.eclipse.dltk.formatter/plugin.properties b/core/plugins/org.eclipse.dltk.formatter/plugin.properties
new file mode 100644
index 0000000..7adcbbf
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/plugin.properties
@@ -0,0 +1,11 @@
+###############################################################################
+# Copyright (c) 2005, 2007 IBM Corporation and others.
+# All rights reserved. This program and the accompanying materials
+# are made available under the terms of the Eclipse Public License v1.0
+# which accompanies this distribution, and is available at
+# http://www.eclipse.org/legal/epl-v10.html
+#
+
+###############################################################################
+pluginProvider=Eclipse.org
+pluginName=Dynamic Languages Toolkit Formatter (Incubation)
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNode.java
new file mode 100644
index 0000000..8037dd5
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNode.java
@@ -0,0 +1,48 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public abstract class AbstractFormatterNode implements IFormatterNode {
+
+	private final IFormatterDocument document;
+
+	/**
+	 * @param document
+	 */
+	public AbstractFormatterNode(IFormatterDocument document) {
+		this.document = document;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterNode#getDocument()
+	 */
+	public IFormatterDocument getDocument() {
+		return document;
+	}
+
+	protected String getShortClassName() {
+		final String name = getClass().getName();
+		int index = name.lastIndexOf('.');
+		return index > 0 ? name.substring(index + 1) : name;
+	}
+
+	/*
+	 * @see java.lang.Object#toString()
+	 */
+	public String toString() {
+		return getShortClassName();
+	}
+
+	protected int getInt(String key) {
+		return document.getInt(key);
+	}
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNodeBuilder.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNodeBuilder.java
new file mode 100644
index 0000000..4e3d9bb
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/AbstractFormatterNodeBuilder.java
@@ -0,0 +1,66 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.Stack;
+
+public class AbstractFormatterNodeBuilder {
+
+	private final Stack stack = new Stack();
+
+	protected void start(IFormatterContainerNode root) {
+		stack.clear();
+		stack.push(root);
+	}
+
+	protected IFormatterContainerNode peek() {
+		return (IFormatterContainerNode) stack.peek();
+	}
+
+	protected void push(IFormatterContainerNode node) {
+		addChild(node);
+		stack.push(node);
+	}
+
+	protected void addChild(IFormatterNode node) {
+		IFormatterContainerNode parentNode = peek();
+		if (!node.isEmpty()) {
+			advanceParent(parentNode, node.getStartOffset());
+		}
+		parentNode.addChild(node);
+	}
+
+	private void advanceParent(IFormatterContainerNode parentNode, final int pos) {
+		if (parentNode.getEndOffset() < pos) {
+			parentNode.addChild(createTextNode(parentNode.getDocument(),
+					parentNode.getEndOffset(), pos));
+		}
+	}
+
+	protected void checkedPop(IFormatterContainerNode expected, int bodyEnd) {
+		if (stack.pop() != expected) {
+			throw new IllegalStateException();
+		}
+		if (bodyEnd > 0 && expected.getEndOffset() < bodyEnd) {
+			expected.addChild(createTextNode(expected.getDocument(), expected
+					.getEndOffset(), bodyEnd));
+		}
+	}
+
+	/**
+	 * @return
+	 */
+	protected IFormatterTextNode createTextNode(IFormatterDocument document,
+			int startIndex, int endIndex) {
+		return new FormatterTextNode(document, startIndex, endIndex);
+	}
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterBlockNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterBlockNode.java
new file mode 100644
index 0000000..8b2b474
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterBlockNode.java
@@ -0,0 +1,107 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.List;
+
+public class FormatterBlockNode extends AbstractFormatterNode implements
+		IFormatterContainerNode {
+
+	/**
+	 * @param document
+	 */
+	public FormatterBlockNode(IFormatterDocument document) {
+		super(document);
+	}
+
+	private final List body = new ArrayList();
+
+	protected void acceptNodes(final List nodes, IFormatterContext context,
+			IFormatterWriter visitor) throws Exception {
+		for (Iterator i = nodes.iterator(); i.hasNext();) {
+			IFormatterNode node = (IFormatterNode) i.next();
+			context.enter(node);
+			node.accept(context, visitor);
+			context.leave(node);
+		}
+	}
+
+	public void addChild(IFormatterNode child) {
+		body.add(child);
+	}
+
+	public void accept(IFormatterContext context, IFormatterWriter visitor)
+			throws Exception {
+		acceptBody(context, visitor);
+	}
+
+	protected void acceptBody(IFormatterContext context,
+			IFormatterWriter visitor) throws Exception {
+		acceptNodes(body, context, visitor);
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterNode#getEndOffset()
+	 */
+	public int getEndOffset() {
+		if (!body.isEmpty()) {
+			return ((IFormatterNode) body.get(body.size() - 1)).getEndOffset();
+		} else {
+			return DEFAULT_OFFSET;
+		}
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterNode#getStartOffset()
+	 */
+	public int getStartOffset() {
+		if (!body.isEmpty()) {
+			return ((IFormatterNode) body.get(0)).getStartOffset();
+		} else {
+			return DEFAULT_OFFSET;
+		}
+	}
+
+	/*
+	 * @see
+	 * org.eclipse.dltk.ruby.formatter.node.IFormatterContainerNode#isEmpty()
+	 */
+	public boolean isEmpty() {
+		for (Iterator i = body.iterator(); i.hasNext();) {
+			IFormatterNode node = (IFormatterNode) i.next();
+			if (!node.isEmpty()) {
+				return false;
+			}
+		}
+		return true;
+	}
+
+	public List getChildren() {
+		return Collections.unmodifiableList(body);
+	}
+
+	public String toString() {
+		return body.toString();
+	}
+
+	protected boolean isIndenting() {
+		return true;
+	}
+
+	public List getBody() {
+		return body;
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterContext.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterContext.java
new file mode 100644
index 0000000..ca5b2aa
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterContext.java
@@ -0,0 +1,152 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.eclipse.dltk.core.DLTKCore;
+
+public class FormatterContext implements IFormatterContext, Cloneable {
+
+	private static class PathEntry {
+		final IFormatterNode node;
+		int childIndex = 0;
+
+		/**
+		 * @param node
+		 */
+		public PathEntry(IFormatterNode node) {
+			this.node = node;
+		}
+
+	}
+
+	private int indent;
+	private boolean indenting = true;
+	private boolean wrapping = false;
+	private int blankLines = 0;
+	private final List path = new ArrayList();
+
+	public FormatterContext(int indent) {
+		this.indent = indent;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterContext#copy()
+	 */
+	public IFormatterContext copy() {
+		try {
+			return (IFormatterContext) clone();
+		} catch (CloneNotSupportedException e) {
+			DLTKCore.error("FormatterContext.copy() error", e); //$NON-NLS-1$
+			throw new IllegalStateException();
+		}
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterContext#decIndent()
+	 */
+	public void decIndent() {
+		--indent;
+		assert indent >= 0;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterContext#incIndent()
+	 */
+	public void incIndent() {
+		++indent;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterContext#resetIndent()
+	 */
+	public void resetIndent() {
+		indent = 0;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterContext#getIndent()
+	 */
+	public int getIndent() {
+		return indent;
+	}
+
+	public boolean isIndenting() {
+		return indenting;
+	}
+
+	public void setIndenting(boolean value) {
+		this.indenting = value;
+	}
+
+	public int getBlankLines() {
+		return blankLines;
+	}
+
+	public void resetBlankLines() {
+		blankLines = -1;
+	}
+
+	public void setBlankLines(int value) {
+		if (value >= 0 && value > blankLines) {
+			blankLines = value;
+		}
+	}
+
+	public void enter(IFormatterNode node) {
+		path.add(new PathEntry(node));
+	}
+
+	public void leave(IFormatterNode node) {
+		final PathEntry entry = (PathEntry) path.remove(path.size() - 1);
+		if (entry.node != node) {
+			throw new IllegalStateException("leave() - node mismatch"); //$NON-NLS-1$
+		}
+		if (!path.isEmpty() && isCountable(node)) {
+			final PathEntry parent = (PathEntry) path.get(path.size() - 1);
+			++parent.childIndex;
+		}
+	}
+
+	protected boolean isCountable(IFormatterNode node) {
+		return true;
+	}
+
+	public IFormatterNode getParent() {
+		if (path.size() > 1) {
+			final PathEntry entry = (PathEntry) path.get(path.size() - 2);
+			return entry.node;
+		} else {
+			return null;
+		}
+	}
+
+	public int getChildIndex() {
+		if (path.size() > 1) {
+			final PathEntry entry = (PathEntry) path.get(path.size() - 2);
+			return entry.childIndex;
+		} else {
+			return -1;
+		}
+	}
+
+	public boolean isWrapping() {
+		return wrapping;
+	}
+
+	public void setWrapping(boolean value) {
+		this.wrapping = value;
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterDocument.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterDocument.java
new file mode 100644
index 0000000..7acc758
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterDocument.java
@@ -0,0 +1,76 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import org.eclipse.jface.text.IRegion;
+
+public class FormatterDocument implements IFormatterDocument {
+
+	private final String text;
+	private final Map booleans = new HashMap();
+	private final Map ints = new HashMap();
+
+	/**
+	 * @param text
+	 */
+	public FormatterDocument(String text) {
+		this.text = text;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterDocument#getText()
+	 */
+	public String getText() {
+		return text;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterDocument#getLength()
+	 */
+	public int getLength() {
+		return text.length();
+	}
+
+	public String get(int startOffset, int endOffset) {
+		return text.substring(startOffset, endOffset);
+	}
+
+	public String get(IRegion region) {
+		return get(region.getOffset(), region.getOffset() + region.getLength());
+	}
+
+	public void setBoolean(String key, boolean value) {
+		booleans.put(key, Boolean.valueOf(value));
+	}
+
+	public boolean getBoolean(String key) {
+		final Boolean value = (Boolean) booleans.get(key);
+		return value != null && value.booleanValue();
+	}
+
+	public void setInt(String key, int value) {
+		ints.put(key, new Integer(value));
+	}
+
+	public int getInt(String key) {
+		final Integer value = (Integer) ints.get(key);
+		return value != null ? value.intValue() : 0;
+	}
+
+	public char charAt(int index) {
+		return text.charAt(index);
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterIndentDetector.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterIndentDetector.java
new file mode 100644
index 0000000..e4d7c75
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterIndentDetector.java
@@ -0,0 +1,57 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import org.eclipse.jface.text.IRegion;
+
+public class FormatterIndentDetector implements IFormatterWriter {
+
+	private final int offset;
+	private boolean indentDetected = false;
+	private int level;
+
+	/**
+	 * @param offset
+	 */
+	public FormatterIndentDetector(int offset) {
+		this.offset = offset;
+	}
+
+	public void addNewLineCallback(IFormatterCallback callback) {
+		// empty
+	}
+
+	public void excludeRegion(IRegion region) {
+		// empty
+
+	}
+
+	public void ensureLineStarted(IFormatterContext context) throws Exception {
+		// empty
+	}
+
+	public void write(IFormatterContext context, int startOffset, int endOffset)
+			throws Exception {
+		if (!indentDetected && startOffset >= offset) {
+			level = context.getIndent();
+			indentDetected = true;
+		}
+	}
+
+	/**
+	 * @return the level
+	 */
+	public int getLevel() {
+		return level;
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterTextNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterTextNode.java
new file mode 100644
index 0000000..bb4e01c
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterTextNode.java
@@ -0,0 +1,127 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.io.StringWriter;
+
+public class FormatterTextNode extends AbstractFormatterNode implements
+		IFormatterTextNode {
+
+	private final int startOffset;
+	private final int endOffset;
+
+	/**
+	 * @param text
+	 */
+	public FormatterTextNode(IFormatterDocument document, int startOffset,
+			int endOffset) {
+		super(document);
+		this.startOffset = startOffset;
+		this.endOffset = endOffset;
+	}
+
+	public String getText() {
+		return getDocument().get(startOffset, endOffset);
+	}
+
+	public void accept(IFormatterContext context, IFormatterWriter visitor)
+			throws Exception {
+		visitor.write(context, getStartOffset(), getEndOffset());
+	}
+
+	public boolean isEmpty() {
+		return false;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterNode#getEndOffset()
+	 */
+	public int getEndOffset() {
+		return endOffset;
+	}
+
+	/*
+	 * @see org.eclipse.dltk.ruby.formatter.node.IFormatterNode#getStartOffset()
+	 */
+	public int getStartOffset() {
+		return startOffset;
+	}
+
+	public String toString() {
+		final StringWriter w = new StringWriter();
+		escapeJavaStyleString(w, getText());
+		return w.toString();
+	}
+
+	private static void escapeJavaStyleString(StringWriter out, String str) {
+		if (str == null) {
+			return;
+		}
+		int sz;
+		sz = str.length();
+		for (int i = 0; i < sz; i++) {
+			char ch = str.charAt(i);
+			if (ch > 0xfff) {
+				out.write("\\u" + hex(ch)); //$NON-NLS-1$
+			} else if (ch > 0xff) {
+				out.write("\\u0" + hex(ch)); //$NON-NLS-1$
+			} else if (ch > 0x7f) {
+				out.write("\\u00" + hex(ch)); //$NON-NLS-1$
+			} else if (ch < 32) {
+				switch (ch) {
+				case '\b':
+					out.write('\\');
+					out.write('b');
+					break;
+				case '\n':
+					out.write('\\');
+					out.write('n');
+					break;
+				case '\t':
+					out.write('\\');
+					out.write('t');
+					break;
+				case '\f':
+					out.write('\\');
+					out.write('f');
+					break;
+				case '\r':
+					out.write('\\');
+					out.write('r');
+					break;
+				default:
+					if (ch > 0xf) {
+						out.write("\\u00" + hex(ch)); //$NON-NLS-1$
+					} else {
+						out.write("\\u000" + hex(ch)); //$NON-NLS-1$
+					}
+					break;
+				}
+			} else {
+				switch (ch) {
+				case '\\':
+					out.write('\\');
+					out.write('\\');
+					break;
+				default:
+					out.write(ch);
+					break;
+				}
+			}
+		}
+	}
+
+	private static String hex(char ch) {
+		return Integer.toHexString(ch).toUpperCase();
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterUtils.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterUtils.java
new file mode 100644
index 0000000..bbf3047
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterUtils.java
@@ -0,0 +1,81 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.List;
+
+public class FormatterUtils {
+
+	public static boolean isSpace(char c) {
+		return c == '\t' || c == ' ';
+	}
+
+	public static boolean isLineSeparator(char c) {
+		return c == '\r' || c == '\n';
+	}
+
+	public static boolean isNewLine(IFormatterNode node) {
+		if (node instanceof IFormatterTextNode) {
+			final IFormatterTextNode textNode = (IFormatterTextNode) node;
+			final IFormatterDocument document = node.getDocument();
+			int start = textNode.getStartOffset();
+			if (start < textNode.getEndOffset()) {
+				if (document.charAt(start) == '\n') {
+					++start;
+				} else if (document.charAt(start) == '\r') {
+					++start;
+					if (start < textNode.getEndOffset()
+							&& document.charAt(start) == '\n') {
+						++start;
+					}
+				} else {
+					return false;
+				}
+			}
+			while (start < textNode.getEndOffset()) {
+				if (!isSpace(document.charAt(start))) {
+					return false;
+				}
+				++start;
+			}
+			return true;
+		}
+		return false;
+	}
+
+	/**
+	 * @param node
+	 * @return
+	 */
+	public static boolean isEmptyText(IFormatterNode node) {
+		if (node instanceof IFormatterTextNode) {
+			final String text = ((IFormatterTextNode) node).getText();
+			for (int i = 0; i < text.length(); ++i) {
+				char c = text.charAt(i);
+				if (!Character.isWhitespace(c)) {
+					return false;
+				}
+			}
+		}
+		return true;
+	}
+
+	public static IFormatterTextNode[] toTextNodeArray(List list) {
+		if (list != null) {
+			return (IFormatterTextNode[]) list
+					.toArray(new IFormatterTextNode[list.size()]);
+		} else {
+			return null;
+		}
+	}
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterWriter.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterWriter.java
new file mode 100644
index 0000000..a6abe59
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/FormatterWriter.java
@@ -0,0 +1,301 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.dltk.formatter.internal.ExcludeRegionList;
+import org.eclipse.dltk.ui.formatter.IFormatterIndentGenerator;
+import org.eclipse.dltk.utils.TextUtils;
+import org.eclipse.jface.text.IRegion;
+
+public class FormatterWriter implements IFormatterWriter {
+
+	private final StringBuffer writer = new StringBuffer();
+	private final StringBuffer indent = new StringBuffer();
+	private final StringBuffer callbackBuffer = new StringBuffer();
+	private final StringBuffer emptyLines = new StringBuffer();
+
+	private boolean lineStarted = false;
+	private char lastChar = 0;
+	private int lineNumber = 0;
+	private final List newLineCallbacks = new ArrayList();
+
+	private final String lineDelimiter;
+	private final IFormatterDocument document;
+	private final IFormatterIndentGenerator indentGenerator;
+	private int linesPreserve = -1;
+	private final int wrapLength;
+
+	/**
+	 * @param lineDelimiter
+	 */
+	public FormatterWriter(IFormatterDocument document, String lineDelimiter,
+			IFormatterIndentGenerator indentGenerator, int wrapLength) {
+		this.document = document;
+		this.lineDelimiter = lineDelimiter;
+		this.indentGenerator = indentGenerator;
+		this.wrapLength = wrapLength;
+	}
+
+	public void ensureLineStarted(IFormatterContext context) throws Exception {
+		if (!lineStarted) {
+			startLine(context);
+		}
+	}
+
+	public void write(IFormatterContext context, int startOffset, int endOffset)
+			throws Exception {
+		if (!excludes.isExcluded(startOffset, endOffset)) {
+			write(context, document.get(startOffset, endOffset));
+		} else {
+			final IRegion[] regions = excludes.selectValidRanges(startOffset,
+					endOffset);
+			for (int i = 0; i < regions.length; ++i) {
+				write(context, document.get(regions[i]));
+			}
+		}
+	}
+
+	protected void write(IFormatterContext context, String text)
+			throws IOException {
+		if (!context.isWrapping()) {
+			for (int i = 0; i < text.length(); ++i) {
+				write(context, text.charAt(i));
+			}
+		} else {
+			int offset;
+			int start = findLineStart();
+			if (lineStarted) {
+				offset = calculateOffset(start);
+			} else {
+				offset = 0;
+			}
+			int savedLineNumber = lineNumber;
+			for (int i = 0; i < text.length(); ++i) {
+				final char ch = text.charAt(i);
+				if (lineStarted && !FormatterUtils.isSpace(ch)
+						&& !FormatterUtils.isLineSeparator(ch)) {
+					if (savedLineNumber != lineNumber) {
+						start = findLineStart();
+						offset = calculateOffset(start);
+						savedLineNumber = lineNumber;
+					}
+					if (offset > wrapLength) {
+						int begin = start;
+						while (begin < writer.length()
+								&& FormatterUtils.isSpace(writer.charAt(begin))) {
+							++begin;
+						}
+						if (begin < writer.length()
+								&& writer.charAt(begin) == '#') {
+							++begin;
+						}
+						while (begin < writer.length()
+								&& FormatterUtils.isSpace(writer.charAt(begin))) {
+							++begin;
+						}
+						int wordBegin = writer.length();
+						while (wordBegin > begin
+								&& !FormatterUtils.isSpace(writer
+										.charAt(wordBegin - 1))) {
+							--wordBegin;
+						}
+						int prevWordEnd = wordBegin;
+						while (prevWordEnd > begin
+								&& FormatterUtils.isSpace(writer
+										.charAt(prevWordEnd - 1))) {
+							--prevWordEnd;
+						}
+						if (prevWordEnd > begin) {
+							writer.replace(prevWordEnd, wordBegin,
+									lineDelimiter + "# "); //$NON-NLS-1$
+							start = prevWordEnd + lineDelimiter.length();
+							offset = calculateOffset(start);
+						}
+					}
+				}
+				write(context, ch);
+				++offset;
+			}
+		}
+	}
+
+	private int calculateOffset(int pos) {
+		int offset = 0;
+		while (pos < writer.length()) {
+			char ch = writer.charAt(pos++);
+			if (ch == '\t') {
+				final int tabSize = indentGenerator.getTabSize();
+				offset = (offset + tabSize - 1) / tabSize * tabSize;
+			} else {
+				++offset;
+			}
+		}
+		return offset;
+	}
+
+	private int findLineStart() {
+		int pos = writer.length();
+		while (pos > 0
+				&& !FormatterUtils.isLineSeparator(writer.charAt(pos - 1))) {
+			--pos;
+		}
+		return pos;
+	}
+
+	/**
+	 * @param context
+	 * @param charAt
+	 * @throws IOException
+	 */
+	protected void write(IFormatterContext context, char ch) throws IOException {
+		if (ch == '\n' || ch == '\r') {
+			if (lineStarted) {
+				writer.append(ch);
+				lineStarted = false;
+				if (!newLineCallbacks.isEmpty()) {
+					executeNewLineCallbacks(context);
+					assert newLineCallbacks.isEmpty();
+				}
+			} else if (ch == '\n' && lastChar == '\r') {
+				if (emptyLines.length() == 0) {
+					writer.append(ch); // windows EOL = "\r\n"
+				} else {
+					emptyLines.append(ch);
+				}
+			} else {
+				indent.setLength(0);// add option "trim empty lines"
+				emptyLines.append(ch);
+			}
+		} else if (!lineStarted) {
+			if (Character.isWhitespace(ch)) {
+				indent.append(ch);
+			} else {
+				startLine(context);
+				writer.append(ch);
+			}
+		} else {
+			writer.append(ch);
+		}
+		lastChar = ch;
+	}
+
+	private void executeNewLineCallbacks(IFormatterContext context) {
+		final IFormatterRawWriter callbackWriter = new IFormatterRawWriter() {
+
+			public void writeIndent(IFormatterContext context) {
+				FormatterWriter.this.writeIndent(context, callbackBuffer);
+			}
+
+			public void writeText(IFormatterContext context, String text) {
+				callbackBuffer.append(text);
+			}
+
+		};
+		final List copy = new ArrayList(newLineCallbacks);
+		newLineCallbacks.clear();
+		for (Iterator i = copy.iterator(); i.hasNext();) {
+			IFormatterCallback callback = (IFormatterCallback) i.next();
+			callback.call(context, callbackWriter);
+		}
+	}
+
+	private void startLine(IFormatterContext context) throws IOException {
+		if (callbackBuffer.length() != 0) {
+			writer.append(callbackBuffer);
+			callbackBuffer.setLength(0);
+		}
+		if (context.getBlankLines() >= 0) {
+			if (writer.length() != 0) {
+				for (int i = 0; i < context.getBlankLines(); ++i) {
+					writer.append(lineDelimiter);
+				}
+			}
+			context.resetBlankLines();
+		} else if (emptyLines.length() != 0) {
+			writeEmptyLines();
+		}
+		emptyLines.setLength(0);
+		if (context.isIndenting()) {
+			writeIndent(context);
+		} else {
+			writer.append(indent);
+		}
+		indent.setLength(0);
+		lineStarted = true;
+		++lineNumber;
+	}
+
+	private void writeEmptyLines() {
+		if (linesPreserve >= 0 && linesPreserve < Integer.MAX_VALUE
+				&& TextUtils.countLines(emptyLines) > linesPreserve) {
+			writer.append(TextUtils.selectHeadLines(emptyLines, linesPreserve));
+		} else {
+			writer.append(emptyLines);
+		}
+	}
+
+	/**
+	 * @param context
+	 */
+	protected void writeIndent(IFormatterContext context) {
+		writeIndent(context, writer);
+	}
+
+	protected void writeIndent(IFormatterContext context, StringBuffer buffer) {
+		indentGenerator.generateIndent(context.getIndent(), buffer);
+	}
+
+	public String getOutput() {
+		return writer.toString();
+	}
+
+	private final ExcludeRegionList excludes = new ExcludeRegionList();
+
+	public void excludeRegion(IRegion region) {
+		excludes.excludeRegion(region);
+	}
+
+	public void addNewLineCallback(IFormatterCallback callback) {
+		newLineCallbacks.add(callback);
+	}
+
+	public void flush(IFormatterContext context) {
+		if (!newLineCallbacks.isEmpty()) {
+			if (lineStarted) {
+				writer.append(lineDelimiter);
+				lineStarted = false;
+			}
+			executeNewLineCallbacks(context);
+			assert newLineCallbacks.isEmpty();
+		}
+		if (callbackBuffer.length() != 0) {
+			writer.append(callbackBuffer);
+			callbackBuffer.setLength(0);
+		}
+		if (emptyLines.length() != 0) {
+			writeEmptyLines();
+			emptyLines.setLength(0);
+		}
+	}
+
+	/**
+	 * @param value
+	 */
+	public void setLinesPreserve(int value) {
+		this.linesPreserve = value;
+	}
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCallback.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCallback.java
new file mode 100644
index 0000000..48e4fb9
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCallback.java
@@ -0,0 +1,22 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public interface IFormatterCallback {
+
+	/**
+	 * @param context
+	 * @param formatterWriter
+	 */
+	void call(IFormatterContext context, IFormatterRawWriter writer);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCommentableNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCommentableNode.java
new file mode 100644
index 0000000..c699570
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterCommentableNode.java
@@ -0,0 +1,9 @@
+package org.eclipse.dltk.formatter;
+
+import java.util.List;
+
+public interface IFormatterCommentableNode {
+
+	void insertBefore(List comments);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContainerNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContainerNode.java
new file mode 100644
index 0000000..c08d984
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContainerNode.java
@@ -0,0 +1,28 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import java.util.List;
+
+
+
+public interface IFormatterContainerNode extends IFormatterNode {
+
+	void addChild(IFormatterNode child);
+
+	boolean isEmpty();
+
+	List getBody();
+
+	List getChildren();
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContext.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContext.java
new file mode 100644
index 0000000..6797379
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterContext.java
@@ -0,0 +1,54 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public interface IFormatterContext {
+
+	int getIndent();
+
+	void incIndent();
+
+	void decIndent();
+
+	void resetIndent();
+
+	IFormatterContext copy();
+
+	boolean isIndenting();
+
+	void setIndenting(boolean valud);
+
+	int getBlankLines();
+
+	void setBlankLines(int value);
+
+	void resetBlankLines();
+
+	/**
+	 * @param node
+	 */
+	void enter(IFormatterNode node);
+
+	/**
+	 * @param node
+	 */
+	void leave(IFormatterNode node);
+
+	IFormatterNode getParent();
+
+	int getChildIndex();
+
+	boolean isWrapping();
+
+	void setWrapping(boolean value);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterDocument.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterDocument.java
new file mode 100644
index 0000000..f97a940
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterDocument.java
@@ -0,0 +1,45 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import org.eclipse.jface.text.IRegion;
+
+public interface IFormatterDocument {
+
+	String getText();
+
+	int getLength();
+
+	/**
+	 * @param startOffset
+	 * @param endOffset
+	 * @return
+	 */
+	String get(int startOffset, int endOffset);
+
+	/**
+	 * @param region
+	 * @return
+	 */
+	String get(IRegion region);
+
+	/**
+	 * @param key
+	 * @return
+	 */
+	boolean getBoolean(String key);
+
+	int getInt(String key);
+
+	char charAt(int start);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterNode.java
new file mode 100644
index 0000000..dce6331
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterNode.java
@@ -0,0 +1,29 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public interface IFormatterNode {
+
+	int DEFAULT_OFFSET = 0;
+
+	void accept(IFormatterContext context, IFormatterWriter visitor)
+			throws Exception;
+
+	boolean isEmpty();
+
+	int getStartOffset();
+
+	int getEndOffset();
+
+	IFormatterDocument getDocument();
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterRawWriter.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterRawWriter.java
new file mode 100644
index 0000000..bac9344
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterRawWriter.java
@@ -0,0 +1,20 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public interface IFormatterRawWriter {
+
+	void writeText(IFormatterContext context, String text);
+
+	void writeIndent(IFormatterContext context);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterTextNode.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterTextNode.java
new file mode 100644
index 0000000..a402615
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterTextNode.java
@@ -0,0 +1,18 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+public interface IFormatterTextNode extends IFormatterNode {
+
+	String getText();
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterWriter.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterWriter.java
new file mode 100644
index 0000000..cf609bf
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/IFormatterWriter.java
@@ -0,0 +1,27 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter;
+
+import org.eclipse.jface.text.IRegion;
+
+public interface IFormatterWriter {
+
+	void ensureLineStarted(IFormatterContext context) throws Exception;
+
+	void write(IFormatterContext context, int startOffset, int endOffset)
+			throws Exception;
+
+	void excludeRegion(IRegion region);
+
+	void addNewLineCallback(IFormatterCallback callback);
+
+}
diff --git a/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/internal/ExcludeRegionList.java b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/internal/ExcludeRegionList.java
new file mode 100644
index 0000000..a37fad7
--- /dev/null
+++ b/core/plugins/org.eclipse.dltk.formatter/src/org/eclipse/dltk/formatter/internal/ExcludeRegionList.java
@@ -0,0 +1,108 @@
+/*******************************************************************************
+ * Copyright (c) 2008 xored software, Inc.
+ *
+ * All rights reserved. This program and the accompanying materials
+ * are made available under the terms of the Eclipse Public License v1.0
+ * which accompanies this distribution, and is available at
+ * http://www.eclipse.org/legal/epl-v10.html
+ *
+ * Contributors:
+ *     xored software, Inc. - initial API and Implementation (Alex Panchenko)
+ *******************************************************************************/
+package org.eclipse.dltk.formatter.internal;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.Iterator;
+import java.util.List;
+
+import org.eclipse.jface.text.IRegion;
+import org.eclipse.jface.text.Region;
+
+public class ExcludeRegionList {
+
+	private final List excludes = new ArrayList();
+
+	public boolean isExcluded(int start, int end) {
+		if (!excludes.isEmpty()) {
+			for (Iterator i = excludes.iterator(); i.hasNext();) {
+				final IRegion region = (IRegion) i.next();
+				final int regionEnd = region.getOffset() + region.getLength();
+				if (start <= regionEnd && region.getOffset() <= end) {
+					return true;
+				}
+			}
+		}
+		return false;
+	}
+
+	public IRegion[] selectValidRanges(int start, int end) {
+		final List result = new ArrayList();
+		for (Iterator i = excludes.iterator(); i.hasNext();) {
+			final IRegion region = (IRegion) i.next();
+			final int regionEnd = region.getOffset() + region.getLength();
+			if (start <= regionEnd && region.getOffset() <= end) {
+				if (start < region.getOffset()) {
+					int validEnd = Math.min(end, region.getOffset());
+					result.add(new Region(start, validEnd - start));
+				}
+				start = regionEnd;
+				if (start > end) {
+					break;
+				}
+			}
+		}
+		if (start < end) {
+			result.add(new Region(start, end - start));
+		}
+		return (IRegion[]) result.toArray(new IRegion[result.size()]);
+	}
+
+	public List getExcludes() {
+		return Collections.unmodifiableList(excludes);
+	}
+
+	public void excludeRegion(IRegion region) {
+		int start = region.getOffset();
+		int end = region.getOffset() + region.getLength();
+		if (!excludes.isEmpty()) {
+			for (Iterator i = excludes.iterator(); i.hasNext();) {
+				final IRegion r = (IRegion) i.next();
+				final int rEnd = r.getOffset() + r.getLength();
+				if (r.getOffset() <= end && start <= rEnd) {
+					if (region.getOffset() >= r.getOffset()
+							&& region.getOffset() + region.getLength() <= rEnd) {
+						// new region is inside one of the old regions
+						return;
+					}
+					// calculate the surrounding bounds
+					if (r.getOffset() < start) {
+						start = r.getOffset();
+					}
+					if (rEnd > end) {
+						end = rEnd;
+					}
+					i.remove();
+				}
+			}
+		}
+		// use input region or create the new one
+		if (start == region.getOffset()
+				&& end == region.getOffset() + region.getLength()) {
+			excludes.add(region);
+		} else {
+			excludes.add(new Region(start, end - start));
+		}
+		Collections.sort(excludes, REGION_COMPARATOR);
+	}
+
+	private static final Comparator REGION_COMPARATOR = new Comparator() {
+
+		public int compare(Object o1, Object o2) {
+			return ((IRegion) o1).getOffset() - ((IRegion) o2).getOffset();
+		}
+
+	};
+
+}