package com.redhat.ceylon.eclipse.code.editor;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.editorJ2C;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.utilJ2C;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getSelection;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import org.antlr.runtime.BufferedTokenStream;
import org.antlr.runtime.CommonToken;
import org.antlr.runtime.Token;
import org.antlr.runtime.TokenSource;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.ITextSelection;
import org.eclipse.jface.text.TextSelection;
import org.eclipse.jface.viewers.ISelectionProvider;
import org.eclipse.ltk.core.refactoring.DocumentChange;
import org.eclipse.text.edits.ReplaceEdit;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Body;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.Statement;
import com.redhat.ceylon.eclipse.code.correct.correctJ2C;
import com.redhat.ceylon.eclipse.code.parse.CeylonParseController;
import com.redhat.ceylon.eclipse.code.parse.TreeLifecycleListener.Stage;
import com.redhat.ceylon.eclipse.code.style.CeylonStyle;
import com.redhat.ceylon.eclipse.ui.CeylonPlugin;
import com.redhat.ceylon.eclipse.util.EditorUtil;
import com.redhat.ceylon.eclipse.util.Nodes;
import com.redhat.ceylon.eclipse.util.StringBuilderWriter;
import com.redhat.ceylon.ide.common.refactoring.DefaultRegion;
import ceylon.formatter.format_;
import ceylon.formatter.options.FormattingOptions;
import ceylon.formatter.options.SparseFormattingOptions;
import ceylon.formatter.options.combinedOptions_;
import ceylon.formatter.options.loadProfile_;
import ceylon.language.AssertionError;
import ceylon.language.Singleton;
final class FormatAction extends Action {
private final CeylonEditor editor;
private final boolean respectSelection;
FormatAction(CeylonEditor editor) {
this(editor, true);
}
FormatAction(CeylonEditor editor, boolean respectSelection) {
super(null);
this.editor = editor;
this.respectSelection = respectSelection;
}
@Override
public boolean isEnabled() {
CeylonParseController cpc = editor.getParseController();
return isEnabled(cpc);
}
public static boolean isEnabled(CeylonParseController cpc) {
return cpc!=null &&
cpc.getStage().ordinal()>=Stage.SYNTACTIC_ANALYSIS.ordinal() &&
cpc.getParsedRootNode()!=null;
}
@Deprecated
private static class FormattingUnit {
public final Node node;
public final CommonToken startToken;
public final CommonToken endToken;
public FormattingUnit(final Node node, final CommonToken startToken, final CommonToken endToken) {
this.node = node;
this.startToken = startToken;
this.endToken= endToken;
}
}
@Override
public void run() {
IDocument document = editor.getCeylonSourceViewer().getDocument();
final ITextSelection ts = getSelection(editor);
final boolean selected = respectSelection && ts.getLength() > 0;
final CeylonParseController pc = editor.getParseController();
format(pc, document, ts, selected, editor.getSelectionProvider());
}
public static void format(final CeylonParseController pc,
IDocument document, final ITextSelection ts,
final boolean selected, ISelectionProvider selectionProvider) {
if (!isEnabled(pc)) {
return;
}
FormattingOptions options = loadProfile_.loadProfile(
CeylonStyle.getFormatterProfile(pc.getProject()),
/* inherit = */ false,
/* baseDir = */ pc.getProject().getLocation().toOSString());
com.redhat.ceylon.ide.common.platform.TextChange change = editorJ2C().eclipseFormatAction().format(pc.getParsedRootNode(),
pc.getTokens(), new correctJ2C().newDocument(document), document.getLength(),
new DefaultRegion(ts.getOffset(), ts.getLength()),
CeylonStyle.getEclipseWsOptions(document),
options);
change.apply();
selectionProvider.setSelection(new TextSelection(
(int) change.getOffset(),
(int) change.getLength()));
}
@Deprecated
void oldformat(final CeylonParseController pc,
IDocument document, final ITextSelection ts,
final boolean selected, ISelectionProvider selectionProvider) {
if (!isEnabled(pc)) return;
final List<CommonToken> tokenList = pc.getTokens();
final List<FormattingUnit> formattingUnits;
boolean formatAll = !selected || document.getLength()==ts.getLength();
if (!formatAll) {
// a node was selected, format only that
Node selectedRootNode = Nodes.findNode(pc.getParsedRootNode(), pc.getTokens(), ts);
if (selectedRootNode == null)
return;
if (selectedRootNode instanceof Body || selectedRootNode instanceof CompilationUnit) {
// format only selected statements, not entire body / CU (from now on: body)
Iterator<? extends Statement> it;
if (selectedRootNode instanceof Body) {
it = ((Body)selectedRootNode).getStatements().iterator();
} else {
it = ((CompilationUnit)selectedRootNode).getDeclarations().iterator();
}
Statement stat = null;
formattingUnits = new ArrayList<FormattingUnit>();
int tokenIndex = -1;
// find first selected statement
while (it.hasNext()) {
stat = it.next();
CommonToken start = (CommonToken)stat.getToken();
CommonToken end = (CommonToken)stat.getEndToken();
if (end.getStopIndex() >= ts.getOffset()) {
formattingUnits.add(new FormattingUnit(stat, start, end));
tokenIndex = end.getTokenIndex() + 1;
break;
}
}
// find last selected statement
while (it.hasNext()) {
stat = it.next();
CommonToken start = (CommonToken)stat.getToken();
CommonToken end = (CommonToken)stat.getEndToken();
if (start.getStartIndex() >= ts.getOffset() + ts.getLength()) {
break;
}
formattingUnits.add(new FormattingUnit(stat, tokenList.get(tokenIndex), end));
tokenIndex = end.getTokenIndex() + 1;
}
if (formattingUnits.isEmpty()) {
// possible if the selection spanned the entire content of the body,
// or if the body is empty, etc.
formattingUnits.add(new FormattingUnit(
selectedRootNode,
(CommonToken)selectedRootNode.getToken(),
(CommonToken)selectedRootNode.getEndToken()));
}
} else {
formattingUnits = Collections.singletonList(new FormattingUnit(
selectedRootNode,
(CommonToken)selectedRootNode.getToken(),
(CommonToken)selectedRootNode.getEndToken()));
}
} else {
// format everything
formattingUnits = Collections.singletonList(new FormattingUnit(
pc.getParsedRootNode(),
tokenList.get(0),
tokenList.get(tokenList.size() - 1)));
}
final StringBuilder builder = new StringBuilder(document.getLength());
final SparseFormattingOptions wsOptions = CeylonStyle.getEclipseWsOptions(document);
try {
for (FormattingUnit unit : formattingUnits) {
final int startTokenIndex = unit.startToken.getTokenIndex();
final int endTokenIndex = unit.endToken.getTokenIndex();
// final int startIndex = unit.startToken.getStartIndex();
// final int stopIndex = unit.endToken.getStopIndex();
final TokenSource tokens = new TokenSource() {
int i = startTokenIndex;
@Override
public Token nextToken() {
if (i <= endTokenIndex)
return tokenList.get(i++);
else if (i == endTokenIndex + 1)
return tokenList.get(tokenList.size() - 1); // EOF token
else
return null;
}
@Override
public String getSourceName() {
throw new IllegalStateException("No one should need this");
}
};
final int indentLevel = (int) (utilJ2C().indents()
.getIndent(unit.node, document)
.replace("\t", wsOptions.getIndentMode().indent(1))
.length()
/ utilJ2C().indents().getIndentSpaces());
if (unit != formattingUnits.get(0)) {
// add indentation
builder.append(wsOptions.getIndentMode().indent(indentLevel));
}
format_.format(
unit.node,
combinedOptions_.combinedOptions(
loadProfile_.loadProfile(
CeylonStyle.getFormatterProfile(pc.getProject()),
/* inherit = */ false,
/* baseDir = */ pc.getProject().getLocation().toOSString()),
new Singleton<SparseFormattingOptions>
(SparseFormattingOptions.$TypeDescriptor$, wsOptions)),
new StringBuilderWriter(builder),
new BufferedTokenStream(tokens),
indentLevel
);
if (unit == formattingUnits.get(0)) {
// trim leading indentation (from formatter's indentBefore)
int firstNonWsIndex = 0;
while (Character.isWhitespace(builder.charAt(firstNonWsIndex)))
firstNonWsIndex++;
if (firstNonWsIndex != 0)
builder.delete(0, firstNonWsIndex);
}
}
} catch (Exception e) {
CeylonPlugin.log(IStatus.ERROR, "Error during code formatting", e);
return;
} catch (AssertionError e) {
CeylonPlugin.log(IStatus.ERROR, "Error during code formatting", e);
return;
}
final String text;
if (selected) {
// remove the trailing line break
text = builder.substring(0,
builder.length() - wsOptions.getLineBreak().getText().length());
} else {
text = builder.toString();
}
try {
final int startIndex = formattingUnits.get(0).startToken.getStartIndex();
final int stopIndex = formattingUnits.get(formattingUnits.size() - 1).endToken.getStopIndex();
final int from = formatAll ? 0 : startIndex;
final int length = formatAll ? document.getLength() : stopIndex - startIndex + 1;
if (!document.get(from, length).equals(text)) {
DocumentChange change = new DocumentChange("Format", document);
change.setEdit(new ReplaceEdit(from, length, text));
EditorUtil.performChange(change);
if (selected) {
selectionProvider.setSelection(new TextSelection(startIndex, text.length()));
}
}
}
catch (Exception e) {
e.printStackTrace();
}
}
}