/* * 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 org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.contentassist.ContentAssistEvent; import org.eclipse.jface.text.contentassist.ICompletionListener; import org.eclipse.jface.text.contentassist.ICompletionListenerExtension; import org.eclipse.jface.text.contentassist.ICompletionListenerExtension2; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContentAssistProcessor; import org.eclipse.swt.widgets.Display; import com.google.common.annotations.VisibleForTesting; /** * @author Michal Anglart * */ public class CycledContentAssistProcessor extends DefaultContentAssistProcessor implements IContentAssistProcessor, ICompletionListener, ICompletionListenerExtension, ICompletionListenerExtension2 { private final SuiteSourceAssistantContext assistContext; private final AssitantCallbacks assistant; private final List<RedContentAssistProcessor> processors; private int currentPage; private boolean canReopenAssitantProgramatically; public CycledContentAssistProcessor(final SuiteSourceAssistantContext assistContext, final AssitantCallbacks assistant) { this.assistContext = assistContext; this.assistant = assistant; this.processors = newArrayList(); this.currentPage = 0; this.canReopenAssitantProgramatically = false; } @VisibleForTesting void setCanReopenAssitantProgramatically(final boolean canReopenAssitantProgramatically) { this.canReopenAssitantProgramatically = canReopenAssitantProgramatically; } public void addProcessor(final RedContentAssistProcessor processor) { processors.add(processor); } private RedContentAssistProcessor getCurrentProcessor() { return processors.get(currentPage); } @Override public ICompletionProposal[] computeCompletionProposals(final ITextViewer viewer, final int offset) { final RedContentAssistProcessor nextApplicableProcessor = getNextApplicableProcessor(viewer.getDocument(), offset); assistant.setStatus(nextApplicableProcessor == getCurrentProcessor() ? "" : String.format("Press Ctrl+Space to show %s proposals", nextApplicableProcessor.getProposalsTitle())); final ICompletionProposal[] proposals = getCurrentProcessor().computeCompletionProposals(viewer, offset); currentPage = processors.indexOf(nextApplicableProcessor); return proposals; } private RedContentAssistProcessor getNextApplicableProcessor(final IDocument document, final int offset) { int i = 1; while (!processorFromIndexIsApplicable((currentPage + i) % processors.size(), document, offset)) { i++; if (i > processors.size()) { return processors.get(0); } } return processors.get((currentPage + i) % processors.size()); } private boolean processorFromIndexIsApplicable(final int index, final IDocument document, final int offset) { final RedContentAssistProcessor processor = processors.get(index); try { return processor.isInApplicableContentType(document, offset); } catch (final BadLocationException e) { throw new IllegalStateException("Offset should be always valid!", e); } } @Override public void assistSessionStarted(final ContentAssistEvent event) { if (event.processor == this) { assistContext.refreshPreferences(); canReopenAssitantProgramatically = true; currentPage = 0; } else { canReopenAssitantProgramatically = false; } } @Override public void assistSessionRestarted(final ContentAssistEvent event) { if (event.processor == this) { canReopenAssitantProgramatically = true; currentPage--; if (currentPage < 0) { currentPage = processors.size() - 1; } } else { canReopenAssitantProgramatically = false; } } @Override public void assistSessionEnded(final ContentAssistEvent event) { if (event.processor == this) { currentPage = 0; } } @Override public void applied(final ICompletionProposal proposal) { // this method is called also for processors from which the proposal was not chosen // hence canReopenAssistantProgramatically is holding information which proccessor // is able to open proposals after accepting if (!canReopenAssitantProgramatically) { return; } if (shouldActivateAssist(proposal)) { canReopenAssitantProgramatically = false; Display.getCurrent().asyncExec(new Runnable() { @Override public void run() { assistant.openCompletionProposals(); } }); } for (final Runnable operation : getOperationsAfterAccept(proposal)) { operation.run(); } } private boolean shouldActivateAssist(final ICompletionProposal proposal) { return proposal instanceof RedCompletionProposal && ((RedCompletionProposal) proposal).shouldActivateAssitantAfterAccepting() || proposal instanceof RedCompletionProposalAdapter && ((RedCompletionProposalAdapter) proposal).shouldActivateAssitantAfterAccepting(); } private Collection<Runnable> getOperationsAfterAccept(final ICompletionProposal proposal) { if (proposal instanceof RedCompletionProposal) { return ((RedCompletionProposal) proposal).operationsToPerformAfterAccepting(); } else if (proposal instanceof RedCompletionProposalAdapter) { return ((RedCompletionProposalAdapter) proposal).operationsToPerformAfterAccepting(); } return new ArrayList<>(); } @Override public void selectionChanged(final ICompletionProposal proposal, final boolean smartToggle) { // nothing to do here } @Override public char[] getCompletionProposalAutoActivationCharacters() { return assistContext.getAssistantAutoActivationChars(); } public interface AssitantCallbacks { void setStatus(String title); void openCompletionProposals(); } }