package com.redhat.ceylon.eclipse.code.editor; import static com.redhat.ceylon.eclipse.util.EditorUtil.getSelection; import static java.lang.Math.min; import java.util.List; import org.antlr.runtime.CommonToken; import org.antlr.runtime.Token; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.jface.action.Action; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.ltk.core.refactoring.DocumentChange; import org.eclipse.ltk.core.refactoring.TextChange; import org.eclipse.text.edits.InsertEdit; import org.eclipse.text.edits.MultiTextEdit; import com.redhat.ceylon.compiler.typechecker.parser.CeylonLexer; import com.redhat.ceylon.compiler.typechecker.tree.Node; import com.redhat.ceylon.compiler.typechecker.tree.Tree; import com.redhat.ceylon.compiler.typechecker.tree.Visitor; import com.redhat.ceylon.eclipse.code.parse.CeylonParseController; import com.redhat.ceylon.eclipse.util.EditorUtil; import com.redhat.ceylon.eclipse.util.Nodes; @Deprecated final class TerminateStatementAction extends Action { private final CeylonEditor editor; private int line; private abstract class Processor extends Visitor {} TerminateStatementAction(CeylonEditor editor) { super(null); this.editor = editor; } @Override public void run() { ITextSelection ts = getSelection(editor); String before = editor.getSelectionText(); line = ts.getEndLine(); try { terminateWithSemicolon(); boolean changed; int count=0; do { changed = terminateWithBrace(); count++; } while (changed&&count<5); // IRegion li = editor.getCeylonSourceViewer().getDocument().getLineInformation(line); // editor.getCeylonSourceViewer().getTextWidget().setSelection(li.getOffset()+li.getLength()); if (!editor.getSelectionText().equals(before)) { //if the caret was at the end of the line, //and a semi was added, it winds up selected //so move the caret after the semi IRegion selection = editor.getSelection(); editor.getCeylonSourceViewer().setSelectedRange(selection.getOffset()+1,0); } // change = new DocumentChange("Terminate Statement", doc); // change.setEdit(new MultiTextEdit()); // editor.getParseController().parse(doc, new NullProgressMonitor(), null); // terminateWithParen(doc, change); // EditorUtil.performChange(change); editor.scheduleParsing(); } catch (Exception e) { e.printStackTrace(); } } private int getCodeEnd(IRegion li, String lineText, List<CommonToken> tokens) { int j=lineText.length()-1; for (; j>=0; j--) { int offset = li.getOffset()+j; if (!skipToken(tokens, offset)) break; } int endOfCodeInLine = li.getOffset()+j; return endOfCodeInLine; } private int getCodeStart(IRegion li, String lineText, List<CommonToken> tokens) { int k=0; for (; k<lineText.length(); k++) { int offset = li.getOffset()+k; if (!skipToken(tokens, offset)) break; } int startOfCodeInLine = li.getOffset()+k; return startOfCodeInLine; } // int count(String s, char c) { // int count=0; // for (int i=0; i<s.length(); i++) { // if (s.charAt(i)==c) count++; // } // return count; // } // private void terminateWithParen(final IDocument doc, final TextChange change) // throws Exception { // CompilationUnit rootNode = parse(); // IRegion li = getLineInfo(doc); // String lineText = doc.get(li.getOffset(), li.getLength()); // final List<CommonToken> tokens = editor.getParseController().getTokens(); // final int startOfCodeInLine = getCodeStart(li, lineText, tokens); // final int endOfCodeInLine = getCodeEnd(li, lineText, tokens); // new Visitor() { // @Override // public void visit(Tree.Expression that) { // super.visit(that); // if (that.getStopIndex()<=endOfCodeInLine && // that.getStartIndex()>=startOfCodeInLine) { // if (that.getToken().getType()==CeylonLexer.LPAREN && // that.getEndToken().getType()!=CeylonLexer.RPAREN) { // change.addEdit(new InsertEdit(that.getStopIndex()+1, // ")")); // } // /*try { // String text = doc.get(that.getStartIndex(), // that.getStopIndex()-that.getStartIndex()+1); // StringBuilder terminators = new StringBuilder(); // for (int i=0; i<count(text, '(')-count(text,')'); i++) { // terminators.append(')'); // } // if (terminators.length()!=0) { // change.addEdit(new InsertEdit(that.getStopIndex()+1, // terminators.toString())); // } // } // catch (Exception e) { // e.printStackTrace(); // }*/ // } // } // }.visit(rootNode); // } private boolean terminateWithBrace() throws Exception { IDocument doc = editor.getCeylonSourceViewer().getDocument(); final TextChange change = new DocumentChange("Terminate Statement", doc); change.setEdit(new MultiTextEdit()); CeylonParseController parser = parse(); Tree.CompilationUnit rootNode = parser.getParsedRootNode(); IRegion li = getLineInfo(doc); final String lineText = doc.get(li.getOffset(), li.getLength()); final List<CommonToken> tokens = parser.getTokens(); final int startOfCodeInLine = getCodeStart(li, lineText, tokens); final int endOfCodeInLine = getCodeEnd(li, lineText, tokens); new Processor() { @Override public void visit(Tree.Expression that) { super.visit(that); if (that.getStopIndex()<=endOfCodeInLine && that.getStartIndex()>=startOfCodeInLine) { Token et = that.getMainEndToken(); Token st = that.getMainToken(); if (st!=null && st.getType()==CeylonLexer.LPAREN && (et==null || et.getType()!=CeylonLexer.RPAREN)) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(that.getEndIndex(), ")")); } } } } @Override public void visit(Tree.ParameterList that) { super.visit(that); terminate(that, CeylonLexer.RPAREN, ")"); } public void visit(Tree.IndexExpression that) { super.visit(that); terminate(that, CeylonLexer.RBRACKET, "]"); } @Override public void visit(Tree.TypeParameterList that) { super.visit(that); terminate(that, CeylonLexer.LARGER_OP, ">"); } @Override public void visit(Tree.TypeArgumentList that) { super.visit(that); terminate(that, CeylonLexer.LARGER_OP, ">"); } @Override public void visit(Tree.PositionalArgumentList that) { super.visit(that); Token t = that.getToken(); if (t!=null && t.getType()==CeylonLexer.LPAREN) { //for infix function syntax terminate(that, CeylonLexer.RPAREN, ")"); } } @Override public void visit(Tree.NamedArgumentList that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, " }"); } @Override public void visit(Tree.SequenceEnumeration that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, " }"); } @Override public void visit(Tree.IterableType that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, "}"); } @Override public void visit(Tree.Tuple that) { super.visit(that); terminate(that, CeylonLexer.RBRACKET, "]"); } @Override public void visit(Tree.TupleType that) { super.visit(that); terminate(that, CeylonLexer.RBRACKET, "]"); } @Override public void visit(Tree.ConditionList that) { super.visit(that); if (!that.getMainToken().getText().startsWith("<missing ")) { terminate(that, CeylonLexer.RPAREN, ")"); } } @Override public void visit(Tree.ForIterator that) { super.visit(that); if (!that.getMainToken().getText().startsWith("<missing ")) { terminate(that, CeylonLexer.RPAREN, ")"); } } @Override public void visit(Tree.ImportMemberOrTypeList that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, " }"); } @Override public void visit(Tree.Import that) { if (that.getImportMemberOrTypeList()==null|| that.getImportMemberOrTypeList() .getMainToken().getText().startsWith("<missing ")) { if (!change.getEdit().hasChildren()) { if (that.getImportPath()!=null && that.getImportPath().getStopIndex()<=endOfCodeInLine) { change.addEdit(new InsertEdit(that.getImportPath().getEndIndex(), " { ... }")); } } } super.visit(that); } @Override public void visit(Tree.ImportModule that) { super.visit(that); if (that.getImportPath()!=null || that.getQuotedLiteral()!=null) { terminate(that, CeylonLexer.SEMICOLON, ";"); } if (that.getVersion()==null) { if (!change.getEdit().hasChildren()) { if (that.getImportPath()!=null && that.getImportPath().getStopIndex()<=endOfCodeInLine) { change.addEdit(new InsertEdit(that.getImportPath().getEndIndex(), " \"1.0.0\"")); } } } } @Override public void visit(Tree.ImportModuleList that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, " }"); } @Override public void visit(Tree.PackageDescriptor that) { super.visit(that); terminate(that, CeylonLexer.SEMICOLON, ";"); } @Override public void visit(Tree.Directive that) { super.visit(that); terminate(that, CeylonLexer.SEMICOLON, ";"); } @Override public void visit(Tree.Body that) { super.visit(that); terminate(that, CeylonLexer.RBRACE, " }"); } @Override public void visit(Tree.MetaLiteral that) { super.visit(that); terminate(that, CeylonLexer.BACKTICK, "`"); } @Override public void visit(Tree.StatementOrArgument that) { super.visit(that); if (/*that instanceof Tree.ExecutableStatement && !(that instanceof Tree.ControlStatement) || that instanceof Tree.AttributeDeclaration || that instanceof Tree.MethodDeclaration || that instanceof Tree.ClassDeclaration || that instanceof Tree.InterfaceDeclaration ||*/ that instanceof Tree.SpecifiedArgument) { terminate(that, CeylonLexer.SEMICOLON, ";"); } } private boolean inLine(Node that) { return that.getStartIndex()>=startOfCodeInLine && that.getStartIndex()<=endOfCodeInLine; } /*private void initiate(Node that, int tokenType, String ch) { if (inLine(that)) { Token mt = that.getMainToken(); if (mt==null || mt.getType()!=tokenType || mt.getText().startsWith("<missing ")) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(that.getStartIndex(), ch)); } } } }*/ private void terminate(Node that, int tokenType, String ch) { if (inLine(that)) { Token et = that.getMainEndToken(); if ((et==null || et.getType()!=tokenType) || that.getStopIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(min(endOfCodeInLine,that.getStopIndex())+1, ch)); } } } } @Override public void visit(Tree.ClassDeclaration that) { super.visit(that); if (inLine(that) && that.getParameterList()==null) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(that.getIdentifier().getEndIndex(), "()")); } } } @Override public void visit(Tree.ClassDefinition that) { super.visit(that); if (inLine(that) && that.getParameterList()==null && that.getClassBody()!=null) { /*for (Tree.Statement st: that.getClassBody().getStatements()) { if (st instanceof Tree.Constructor) { return; } }*/ if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(that.getIdentifier().getEndIndex(), "()")); } } } @Override public void visit(Tree.Constructor that) { super.visit(that); if (inLine(that) && that.getParameterList()==null && that.getBlock()!=null) { if (!change.getEdit().hasChildren()) { Tree.Identifier id = that.getIdentifier(); CommonToken tok = (CommonToken) (id==null ? that.getMainToken() : id.getToken()); change.addEdit(new InsertEdit(tok.getStopIndex()+1, "()")); } } } @Override public void visit(Tree.AnyMethod that) { super.visit(that); if (inLine(that) && that.getParameterLists().isEmpty()) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(that.getIdentifier().getEndIndex(), "()")); } } } }.visit(rootNode); if (change.getEdit().hasChildren()) { EditorUtil.performChange(change); return true; } return false; } private boolean terminateWithSemicolon() throws Exception { final IDocument doc = editor.getCeylonSourceViewer().getDocument(); final TextChange change = new DocumentChange("Terminate Statement", doc); change.setEdit(new MultiTextEdit()); CeylonParseController parser = parse(); Tree.CompilationUnit rootNode = parser.getParsedRootNode(); IRegion li = getLineInfo(doc); String lineText = doc.get(li.getOffset(), li.getLength()); final List<CommonToken> tokens = parser.getTokens(); //final int startOfCodeInLine = getCodeStart(li, lineText, tokens); final int endOfCodeInLine = getCodeEnd(li, lineText, tokens); if (!doc.get(endOfCodeInLine,1).equals(";")) { new Processor() { @Override public void visit(Tree.Annotation that) { super.visit(that); terminateWithSemicolon(that); } @Override public void visit(Tree.StaticType that) { super.visit(that); terminateWithSemicolon(that); } @Override public void visit(Tree.Expression that) { super.visit(that); terminateWithSemicolon(that); } boolean terminatedInLine(Node node) { return node!=null && node.getStartIndex()<=endOfCodeInLine; } @Override public void visit(Tree.IfClause that) { super.visit(that); if (missingBlock(that.getBlock()) && terminatedInLine(that.getConditionList())) { terminateWithParenAndBaces(that, that.getConditionList()); } } @Override public void visit(Tree.ElseClause that) { super.visit(that); if (missingBlock(that.getBlock())) { terminateWithBaces(that); } } @Override public void visit(Tree.ForClause that) { super.visit(that); if (missingBlock(that.getBlock()) && terminatedInLine(that.getForIterator())) { terminateWithParenAndBaces(that, that.getForIterator()); } } @Override public void visit(Tree.WhileClause that) { super.visit(that); if (missingBlock(that.getBlock()) && terminatedInLine(that.getConditionList())) { terminateWithParenAndBaces(that, that.getConditionList()); } } @Override public void visit(Tree.CaseClause that) { super.visit(that); if (missingBlock(that.getBlock()) && terminatedInLine(that.getCaseItem())) { terminateWithParenAndBaces(that, that.getCaseItem()); } } @Override public void visit(Tree.TryClause that) { super.visit(that); if (missingBlock(that.getBlock())) { terminateWithBaces(that); } } @Override public void visit(Tree.CatchClause that) { super.visit(that); if (missingBlock(that.getBlock()) && terminatedInLine(that.getCatchVariable())) { terminateWithParenAndBaces(that, that.getCatchVariable()); } } @Override public void visit(Tree.FinallyClause that) { super.visit(that); if (missingBlock(that.getBlock())) { terminateWithBaces(that); } } @Override public void visit(Tree.StatementOrArgument that) { if (that instanceof Tree.ExecutableStatement && !(that instanceof Tree.ControlStatement) || that instanceof Tree.AttributeDeclaration || that instanceof Tree.ImportModule || that instanceof Tree.TypeAliasDeclaration || that instanceof Tree.SpecifiedArgument) { terminateWithSemicolon(that); } if (that instanceof Tree.MethodDeclaration) { Tree.MethodDeclaration md = (Tree.MethodDeclaration) that; if (md.getSpecifierExpression()==null) { List<Tree.ParameterList> pl = md.getParameterLists(); if (md.getIdentifier()!=null && terminatedInLine(md.getIdentifier())) { terminateWithParenAndBaces(that, pl.isEmpty() ? null : pl.get(pl.size()-1)); } } else { terminateWithSemicolon(that); } } if (that instanceof Tree.ClassDeclaration) { Tree.ClassDeclaration cd = (Tree.ClassDeclaration) that; if (cd.getClassSpecifier()==null) { terminateWithParenAndBaces(that, cd.getParameterList()); } else { terminateWithSemicolon(that); } } if (that instanceof Tree.InterfaceDeclaration) { Tree.InterfaceDeclaration id = (Tree.InterfaceDeclaration) that; if (id.getTypeSpecifier()==null) { terminateWithBaces(that); } else { terminateWithSemicolon(that); } } super.visit(that); } private void terminateWithParenAndBaces(Node that, Node subnode) { try { if (withinLine(that)) { if (subnode==null || subnode.getStartIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(endOfCodeInLine+1, "() {}")); } } else { Token et = that.getEndToken(); Token set = subnode.getEndToken(); if (set==null || set.getType()!=CeylonLexer.RPAREN || subnode.getStopIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(endOfCodeInLine+1, ") {}")); } } else if (et==null || et.getType()!=CeylonLexer.RBRACE || that.getStopIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(endOfCodeInLine+1, " {}")); } } } } } catch (Exception e) { e.printStackTrace(); } } private void terminateWithBaces(Node that) { try { if (withinLine(that)) { Token et = that.getEndToken(); if (et==null || et.getType()!=CeylonLexer.SEMICOLON && et.getType()!=CeylonLexer.RBRACE || that.getStopIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(endOfCodeInLine+1, " {}")); } } } } catch (Exception e) { e.printStackTrace(); } } private void terminateWithSemicolon(Node that) { try { if (withinLine(that)) { Token et = that.getEndToken(); if (et==null || et.getType()!=CeylonLexer.SEMICOLON || that.getStopIndex()>endOfCodeInLine) { if (!change.getEdit().hasChildren()) { change.addEdit(new InsertEdit(endOfCodeInLine+1, ";")); } } } } catch (Exception e) { e.printStackTrace(); } } boolean withinLine(Node that) { return that.getStartIndex()!=null && that.getStopIndex()!=null && that.getStartIndex()<=endOfCodeInLine && that.getStopIndex()>=endOfCodeInLine; } protected boolean missingBlock(Tree.Block block) { return block==null || block.getMainToken()==null || block.getMainToken() .getText().startsWith("<missing"); } }.visit(rootNode); if (change.getEdit().hasChildren()) { EditorUtil.performChange(change); return true; } } return false; } private IRegion getLineInfo(final IDocument doc) throws BadLocationException { return doc.getLineInformation(line); } private boolean skipToken(List<CommonToken> tokens, int offset) { int ti = Nodes.getTokenIndexAtCharacter(tokens, offset); if (ti<0) ti=-ti; int type = tokens.get(ti).getType(); return type==CeylonLexer.WS || type==CeylonLexer.MULTI_COMMENT || type==CeylonLexer.LINE_COMMENT; } private CeylonParseController parse() { CeylonParseController cpc = new CeylonParseController(); cpc.initialize(editor.getParseController().getPath(), editor.getParseController().getProject(), null); cpc.parseAndTypecheck(editor.getCeylonSourceViewer().getDocument(), 0, // don't wait for the source model since we don't even need it. new NullProgressMonitor(), null); return cpc; } }