/*******************************************************************************
* Copyright (c) 2012 Sierra Wireless and others.
* All rights reserved. This program and the accompanying materials
* are made available under the terms of the Eclipse Public License v1.0
* which accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors:
* Sierra Wireless - initial API and implementation
*******************************************************************************/
package org.eclipse.koneki.ldt.ui.internal.editor.completion;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.dltk.compiler.CharOperation;
import org.eclipse.dltk.ui.DLTKPluginImages;
import org.eclipse.dltk.ui.text.completion.ContentAssistInvocationContext;
import org.eclipse.dltk.ui.text.completion.IScriptCompletionProposalComputer;
import org.eclipse.dltk.ui.text.completion.ScriptCompletionProposal;
import org.eclipse.dltk.ui.text.completion.ScriptContentAssistInvocationContext;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.jface.text.contentassist.IContextInformation;
import org.eclipse.koneki.ldt.ui.internal.Activator;
import org.eclipse.koneki.ldt.ui.internal.editor.LuaDocumentorTags;
import org.eclipse.koneki.ldt.ui.internal.editor.templates.LuaDocumentorTemplateCompletionProcessor;
import org.eclipse.koneki.ldt.ui.internal.editor.text.ILuaPartitions;
public class LuaDocumentorCompletionProposalComputer implements IScriptCompletionProposalComputer {
public LuaDocumentorCompletionProposalComputer() {
}
public void sessionStarted() {
}
public void sessionEnded() {
}
public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) {
IDocument document = context.getDocument();
try {
final IRegion contentAssistRegion = document.getLineInformationOfOffset(context.getInvocationOffset());
final char[] contentAssistLine = document.get(contentAssistRegion.getOffset(), contentAssistRegion.getLength()).toCharArray();
final int contentAssistOffsetInLine = context.getInvocationOffset() - contentAssistRegion.getOffset();
int offsetInCurrentLine = 0;
// Check if we are on the first line of the lua doc bloc to known how many hyphens we have to ignore
// Find the block region where the content assist is called
ITypedRegion[] partitions = TextUtilities.computePartitioning(document, ILuaPartitions.LUA_PARTITIONING, 0, document.getLength(), false);
for (ITypedRegion region : partitions) {
if (ILuaPartitions.LUA_DOC.equals(region.getType()) || ILuaPartitions.LUA_DOC_MULTI.equals(region.getType())) {
if (context.getInvocationOffset() >= region.getOffset()
&& context.getInvocationOffset() < (region.getOffset() + region.getLength())) {
// "region" is the current region
int blockFirstLine = document.getLineOfOffset(region.getOffset());
int invocationLine = document.getLineOfOffset(context.getInvocationOffset());
boolean isInvocationOnFirstLine = (blockFirstLine == invocationLine);
// On the first line, skip things before the luadoc block
if (isInvocationOnFirstLine) {
offsetInCurrentLine = region.getOffset() - document.getLineOffset(blockFirstLine);
} else {
offsetInCurrentLine = 0;
}
// skip openning comment chars
if (ILuaPartitions.LUA_DOC_MULTI.equals(region.getType())) {
// on multi-line skip "--[[-"
if (isInvocationOnFirstLine) {
offsetInCurrentLine = skipOpenningMultiLineChars(contentAssistLine, offsetInCurrentLine, contentAssistOffsetInLine);
}
} else if (ILuaPartitions.LUA_DOC.equals(region.getType())) {
// skip 3 or 2 hyphens
if (isInvocationOnFirstLine) {
offsetInCurrentLine += 3;
} else {
offsetInCurrentLine += 2;
}
}
}
}
}
offsetInCurrentLine = skipSpaces(contentAssistLine, offsetInCurrentLine, contentAssistOffsetInLine);
// the first char after comment opening have to be a @
if (!(offsetInCurrentLine <= contentAssistOffsetInLine)) {
return Collections.emptyList();
}
// we find the start of the tag
int tagStart = offsetInCurrentLine;
// filter proposals of the text between the @ and the cursor and compute relevance
final boolean endOfLine = (contentAssistLine.length == contentAssistOffsetInLine);
final boolean isCursorFollowedByWhitespace = (!endOfLine && Character.isWhitespace(contentAssistLine[contentAssistOffsetInLine]));
final String partialTag = new String(contentAssistLine, tagStart, contentAssistOffsetInLine - tagStart);
return completionOnTag(context, partialTag, endOfLine, isCursorFollowedByWhitespace);
} catch (BadLocationException e) {
Activator.logError("Compute completion proposal error", e); //$NON-NLS-1$
}
return Collections.emptyList();
}
private static int skipOpenningMultiLineChars(char[] contentAssistLine, int offsetInCurrentLine, int contentAssistOffsetInLine) {
Pattern pattern = Pattern.compile("^--\\[=*\\[-");//$NON-NLS-1$
String startBlockToCursor = new String(contentAssistLine, offsetInCurrentLine, contentAssistOffsetInLine - offsetInCurrentLine);
Matcher matcher = pattern.matcher(startBlockToCursor);
if (matcher.find()) {
return matcher.end() + offsetInCurrentLine;
}
return offsetInCurrentLine;
}
private static int skipSpaces(final char[] line, final int offsetInCurrentLine, final int offsetInLine) {
int newOffsetInCurrentLine = offsetInCurrentLine;
while (newOffsetInCurrentLine < offsetInLine && Character.isWhitespace(line[newOffsetInCurrentLine])) {
newOffsetInCurrentLine++;
}
return newOffsetInCurrentLine;
}
private List<ICompletionProposal> completionOnTag(final ContentAssistInvocationContext context, final String tag,
final boolean nothingAfterOnTheLine, boolean tagFollowedByWhitespace) {
// retrieve templates proposals
final List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>();
final LuaDocumentorTemplateCompletionProcessor processor = new LuaDocumentorTemplateCompletionProcessor(
(ScriptContentAssistInvocationContext) context);
Collections.addAll(proposals, processor.computeCompletionProposals(context.getViewer(), context.getInvocationOffset()));
// retrieve tags
final Set<String> tags = new HashSet<String>();
Collections.addAll(tags, LuaDocumentorTags.getTags());
// add simple tags proposals matching with the given tag
for (String jsdocTag : tags) {
if (CharOperation.prefixEquals(tag, jsdocTag)) {
// if there is nothing after, template are more relevant
int relevance = nothingAfterOnTheLine ? 50 : 95;
// add a space after the replacement if missing
String replacement = jsdocTag;
if (nothingAfterOnTheLine || !tagFollowedByWhitespace) {
replacement += ' ';
}
// add tag proposal
proposals.add(new ScriptCompletionProposal(replacement, context.getInvocationOffset() - tag.length(), tag.length(), DLTKPluginImages
.get(DLTKPluginImages.IMG_OBJS_JAVADOCTAG), jsdocTag, relevance, true));
}
}
return proposals;
}
public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) {
return Collections.emptyList();
}
public String getErrorMessage() {
return null;
}
}