package com.sap.furcas.ide.editor.contentassist; import static com.sap.furcas.ide.editor.contentassist.CompletionListHelper.prefixFilter; import static com.sap.furcas.ide.editor.contentassist.CompletionListHelper.proposalListAsArray; import static com.sap.furcas.ide.editor.contentassist.CompletionListHelper.removeDuplicates; import static com.sap.furcas.ide.editor.contentassist.CompletionListHelper.removeNullValues; import static com.sap.furcas.ide.editor.contentassist.CompletionListHelper.sortProposals; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.computeNonWhitespacePrefix; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.fixTokenText; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.getAbsoluteOffset; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.getDocumentContents; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.getLine; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.isAtEndOfToken; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.isContextAtWhitespace; import static com.sap.furcas.ide.editor.contentassist.CtsContentAssistUtil.isInToken; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.List; import java.util.Map; import org.eclipse.core.runtime.Assert; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.ocl.ecore.opposites.OppositeEndFinder; import com.sap.furcas.ide.editor.contentassist.modeladapter.StubModelAdapter; import com.sap.furcas.ide.editor.document.CtsDocument; import com.sap.furcas.metamodel.FURCAS.TCS.ClassTemplate; import com.sap.furcas.metamodel.FURCAS.TCS.ConcreteSyntax; import com.sap.furcas.metamodel.FURCAS.TCS.OperatorTemplate; import com.sap.furcas.runtime.common.util.TCSSpecificOCLEvaluator; import com.sap.furcas.runtime.parser.IModelAdapter; import com.sap.furcas.runtime.parser.ParserFacade; import com.sap.furcas.runtime.parser.exceptions.InvalidParserImplementationException; import com.sap.furcas.runtime.parser.exceptions.UnknownProductionRuleException; import com.sap.furcas.runtime.parser.impl.DefaultTextAwareModelAdapter; import com.sap.furcas.runtime.parser.impl.DelegationParsingObserver; import com.sap.furcas.runtime.tcs.TcsUtil; import com.sap.furcas.runtime.textblocks.model.TextBlocksModel; import com.sap.ide.cts.parser.incremental.IncrementalParserFacade; /** * Implements a content assists which is used by the editor framework. This * class basically serves as facade. The actual completion is calculated * by {@link CtsCompletionCalculator}. * * It can derive completion proposals based on a given {@link ConcreteSyntax} * and a position within a text document. * * @author Philipp Meier * */ public class CtsContentAssistProcessor { /** * contains a mapping of qualifiedName + Mode to ClassTemplate of all * ClassTemplates contained in the ConcreteSyntax passed to the constructor */ private final Map<List<String>, Map<String, ClassTemplate>> classTemplateMap; /** * contains a mapping of qualifiedName to OperatorTemplate of all * OperatorTemplates contained in the ConcreteSyntax passed to the * constructor */ private final Map<List<String>, OperatorTemplate> operatorTemplateMap; private final ConcreteSyntax syntax; private final ResourceSet resourceSet; private final OppositeEndFinder oppositeEndFinder; private final TCSSpecificOCLEvaluator oclEvaluator; private final ParserFacade facade; private CtsContentAssistParsingHandler parsingHandler = null; public CtsContentAssistProcessor(IncrementalParserFacade parserFacade) { this.syntax = parserFacade.getParserScope().getSyntax(); this.resourceSet = parserFacade.getParserScope().getResourceSet(); Assert.isNotNull(resourceSet, "moin connection is null"); this.oppositeEndFinder = parserFacade.getOppositeEndFinder(); this.oclEvaluator = parserFacade.getOclEvaluator(); classTemplateMap = TcsUtil.createClassTemplateMap(syntax); operatorTemplateMap = TcsUtil.createOperatorTemplateMap(syntax); try { facade = new ParserFacade(parserFacade.getParserFactory().getParserClass(), parserFacade.getParserFactory().getLexerClass(), parserFacade.getParserFactory().getLanguageId()); } catch (InvalidParserImplementationException e) { throw new RuntimeException("Failed to initialize the Content Assist", e); } } /** * offset = 0..n-1 */ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int offset) { try { int line = CtsContentAssistUtil.getLine(viewer, offset); int charPositionInLine = getCharPositionInLine(viewer, offset, line); return computeCompletionProposals(viewer, line, charPositionInLine); } catch (BadLocationException e) { e.printStackTrace(); } return null; } /** * * @param viewer * @param offset * 0..n-1 * @param line * 0..n-1 * @return * @throws BadLocationException */ private static int getCharPositionInLine(ITextViewer viewer, int offset, int line) throws BadLocationException { IRegion lineRegion = viewer.getDocument().getLineInformation(line); return offset - lineRegion.getOffset(); } private ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int line, int charPositionInLine) { TextBlocksModel tbModel = null; IDocument doc = viewer.getDocument(); if (doc instanceof CtsDocument) { tbModel = new TextBlocksModel(((CtsDocument) doc).getRootBlock()); } return computeCompletionProposals(viewer, line, charPositionInLine, tbModel); } /** * * @param viewer * @param line * 0...n * @param charPositionInLine * 0..n * @return */ public ICompletionProposal[] computeCompletionProposals(ITextViewer viewer, int line, int charPositionInLine, TextBlocksModel tbModel) { try { if (inComment(viewer, line, charPositionInLine)) { return null; } List<ICompletionProposal> results = new ArrayList<ICompletionProposal>(); parsingHandler = initParsingHandler(viewer); CtsContentAssistContext context = getContext(line, charPositionInLine); // workaround for ANTLR unlexed tokens that get parsed but start with whitespace if (context != null) { if (isContextAtWhitespace(viewer, context)) { context = getPreviousContext(context); } } String prefix = ""; if (!isValid(context)) { // no floor context, get first possible proposals results = CtsCompletionCalculator.createFirstPossibleProposals(syntax, classTemplateMap, viewer, line, charPositionInLine, null, tbModel, oclEvaluator); // TODO workaround because ANTRL will not create error token // for unlexed characters // TODO this assumes languages with standard whitespaces // filter by currently un-tokenized non-whitespace prefix int curOffset = getAbsoluteOffset(viewer, line, charPositionInLine); // stop just before start of current line int stopOffset = getAbsoluteOffset(viewer, line, 0) - 1; prefix = computeNonWhitespacePrefix(CtsContentAssistUtil.getDocumentContents(viewer), curOffset, stopOffset); results = prefixFilter(removeNullValues(results), prefix); // end workaround } else { if (context.getToken().getText() == null) { // TODO workaround as ANTLR does not create a correct // token for unlexed content fixTokenText(viewer, context.getToken()); } if (isInToken(line, charPositionInLine, context.getToken())) { CtsContentAssistContext previousContext = getPreviousContext(context); // get proposals that follow previous token, and apply // prefix filter if (!isValid(previousContext)) { results = CtsCompletionCalculator.createFirstPossibleProposals(syntax, classTemplateMap, viewer, line, charPositionInLine, context.getToken(), tbModel, oclEvaluator); } else { results = CtsCompletionCalculator.createFollowProposalsFromContext(syntax, previousContext, classTemplateMap, viewer, line, charPositionInLine, context.getToken(), tbModel, oclEvaluator); } // compute prefix from token text prefix = computePrefixFromContext(charPositionInLine, context); results = prefixFilter(removeNullValues(results), prefix); // also get following proposals when after the last char // of a token // TODO maybe make this dependent on token type and // symbol // space variables if (previousContext != null && isAtEndOfToken(line, charPositionInLine, context.getToken())) { results.addAll(CtsCompletionCalculator.createFollowProposalsFromContext(syntax, context, classTemplateMap, viewer, line, charPositionInLine, null, tbModel, oclEvaluator)); // not prefix-filtered } } else { if (!context.isErrorContext()) { // get proposals that follow token results = CtsCompletionCalculator.createFollowProposalsFromContext(syntax, context, classTemplateMap, viewer, line, charPositionInLine, context.getToken(), tbModel, oclEvaluator); } } } return proposalListAsArray(sortProposals(removeDuplicates(removeNullValues(results)), prefix)); } catch (Exception e) { e.printStackTrace(); return null; } finally { // FIXME: This is bad: There is a cache in a static class CtsCompletionCalculator.clearTransientPartition(resourceSet); } } private String computePrefixFromContext(int charPositionInLine, CtsContentAssistContext context) { String prefix; prefix = context.getToken().getText().substring(0, charPositionInLine - context.getToken().getCharPositionInLine()); return prefix; } private boolean inComment(ITextViewer viewer, int line, int charPositionInLine) throws BadLocationException { IRegion lineInfo = viewer.getDocument().getLineInformation(line); String curLine = CtsContentAssistUtil.getDocumentContents(viewer).substring(lineInfo.getOffset(), lineInfo.getOffset() + lineInfo.getLength()); // TODO update this when other forms than end of line comments // become possible int commentStart = curLine.indexOf(TcsUtil.getEndOfLineCommentPrefix(TcsUtil.getCommentToken(syntax))); if (commentStart != -1) { if (charPositionInLine > commentStart) { return true; } } return false; } /** * determines wheater a context is valid and corresponds to parsed token * * @param context * @return */ private boolean isValid(CtsContentAssistContext context) { if (context == null) { return false; } if (context.getToken() == null) { return false; } if (CtsContentAssistUtil.getCharPositionInLine(context.getToken()) == -1) { return false; } return true; } private CtsContentAssistParsingHandler initParsingHandler(ITextViewer viewer) throws IOException, UnknownProductionRuleException { String documentContents = getDocumentContents(viewer); InputStream in = new ByteArrayInputStream(documentContents.getBytes()); IModelAdapter modelHandler = new DefaultTextAwareModelAdapter(new StubModelAdapter()); // use delegator to monitor exceptions DelegationParsingObserver delegator = new DelegationParsingObserver(); CtsContentAssistParsingHandler handler = new CtsContentAssistParsingHandler(resourceSet, classTemplateMap, operatorTemplateMap); delegator.addParsingObserver(handler); facade.parseProductionRule(in, modelHandler, null, null, delegator); return handler; } private CtsContentAssistContext getPreviousContext(CtsContentAssistContext context) { // get the context one offset before this context return getContext(getLine(context.getToken()), CtsContentAssistUtil.getCharPositionInLine(context.getToken()) - 1); } private CtsContentAssistContext getContext(int line, int charPositionInLine) { return parsingHandler.getFloorContext(line, charPositionInLine); } }