/*
* Copyright 2015 Nokia Solutions and Networks
* Licensed under the Apache License, Version 2.0,
* see license.txt file for details.
*/
package org.robotframework.ide.eclipse.main.plugin.tableeditor.source.assist;
import static com.google.common.collect.Lists.newArrayList;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
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.Position;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ContextInformation;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.rf.ide.core.testdata.model.table.keywords.names.EmbeddedKeywordNamesSupport;
import org.robotframework.ide.eclipse.main.plugin.assist.AssistProposal;
import org.robotframework.ide.eclipse.main.plugin.assist.RedKeywordProposal;
import org.robotframework.ide.eclipse.main.plugin.assist.RedKeywordProposals;
import org.robotframework.ide.eclipse.main.plugin.model.locators.KeywordEntity;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.source.DocumentUtilities;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.source.SuiteSourcePartitionScanner;
import org.robotframework.ide.eclipse.main.plugin.tableeditor.source.assist.RedCompletionProposalAdapter.DocumentationModification;
import org.robotframework.red.jface.text.link.RedEditorLinkedModeUI;
import org.robotframework.red.swt.SwtThread;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
/**
* @author Michal Anglart
*/
public class KeywordCallsAssistProcessor extends RedContentAssistProcessor {
public KeywordCallsAssistProcessor(final SuiteSourceAssistantContext assist) {
super(assist);
}
@Override
protected String getProposalsTitle() {
return "Keywords";
}
@Override
protected List<String> getApplicableContentTypes() {
return newArrayList(SuiteSourcePartitionScanner.TEST_CASES_SECTION,
SuiteSourcePartitionScanner.KEYWORDS_SECTION);
}
@Override
protected boolean shouldShowProposals(final IDocument document, final int offset, final String lineContent)
throws BadLocationException {
return isInApplicableContentType(document, offset)
&& DocumentUtilities.getNumberOfCellSeparators(lineContent, assist.isTsvFile()) > 0;
}
@Override
protected List<? extends ICompletionProposal> computeProposals(final IDocument document, final int offset,
final int cellLength, final String prefix, final boolean atTheEndOfLine) throws BadLocationException {
final List<RedKeywordProposal> kwProposals = new RedKeywordProposals(assist.getModel())
.getKeywordProposals(prefix);
final String separator = assist.getSeparatorToFollow();
final List<ICompletionProposal> proposals = newArrayList();
final String lineContent = DocumentUtilities.lineContentBeforeCurrentPosition(document, offset);
for (final AssistProposal kwProposal : kwProposals) {
final List<String> args = atTheEndOfLine ? getArguments(kwProposal, lineContent) : new ArrayList<String>();
final String contentSuffix = args.isEmpty() ? "" : (separator + Joiner.on(separator).join(args));
final Position positionToReplace = new Position(offset - prefix.length(), cellLength);
final Collection<Runnable> operations = atTheEndOfLine ? createOperationsToPerformAfterAccepting(viewer,
(KeywordEntity) kwProposal, positionToReplace.getOffset(), lineContent) : new ArrayList<Runnable>();
final DocumentationModification modification = new DocumentationModification(contentSuffix,
positionToReplace, operations);
final IContextInformation contextInfo = new ContextInformation(null,
((KeywordEntity) kwProposal).getArgumentsDescriptor().getDescription());
proposals.add(new RedCompletionProposalAdapter(kwProposal, modification, contextInfo));
}
return proposals;
}
protected List<String> getArguments(final AssistProposal proposal,
@SuppressWarnings("unused") final String lineContent) {
return proposal.getArguments();
}
private Collection<Runnable> createOperationsToPerformAfterAccepting(final ITextViewer viewer,
final KeywordEntity entity, final int startOffset, final String lineContent) {
final Runnable operation = new Runnable() {
@Override
public void run() {
final Collection<IRegion> regionsToLinkedEdit = calculateRegionsForLinkedMode(entity, startOffset,
lineContent);
if (!regionsToLinkedEdit.isEmpty()) {
SwtThread.asyncExec(new Runnable() {
@Override
public void run() {
RedEditorLinkedModeUI.enableLinkedMode(viewer, regionsToLinkedEdit);
}
});
}
}
};
return newArrayList(operation);
}
@VisibleForTesting
Collection<IRegion> calculateRegionsForLinkedMode(final KeywordEntity entity, final int startOffset,
final String lineContent) {
final AssistProposal proposal = (AssistProposal) entity;
final String keywordName = entity.getNameFromDefinition();
if (EmbeddedKeywordNamesSupport.hasEmbeddedArguments(keywordName)) {
return calculateRegionsForLinkedModeOfEmbeddedKeyword(startOffset, proposal.getContent());
} else {
final int separatorLength = assist.getSeparatorToFollow().length();
return calculateRegionsForLinkedModeOfRegularKeyword(startOffset, proposal.getContent(),
getArguments(proposal, lineContent), separatorLength);
}
}
private Collection<IRegion> calculateRegionsForLinkedModeOfEmbeddedKeyword(final int startOffset,
final String wholeContent) {
final Collection<IRegion> regions = new ArrayList<>();
final Matcher matcher = Pattern.compile("\\$\\{[^\\}]+\\}").matcher(wholeContent);
while (matcher.find()) {
regions.add(new Region(startOffset + matcher.start(), matcher.end() - matcher.start()));
}
return regions;
}
private Collection<IRegion> calculateRegionsForLinkedModeOfRegularKeyword(final int startOffset,
final String wholeContent, final List<String> arguments, final int separatorLength) {
final Collection<IRegion> regions = new ArrayList<>();
int offset = startOffset + wholeContent.length();
if (!arguments.isEmpty()) {
offset += separatorLength;
}
for (final String requiredArg : arguments) {
regions.add(new Region(offset, requiredArg.length()));
offset += requiredArg.length() + separatorLength;
}
return regions;
}
}