package org.eclipse.dltk.javascript.internal.ui.text.completion; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.HashSet; import java.util.List; import java.util.Set; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.dltk.codeassist.ICompletionEngine; import org.eclipse.dltk.compiler.CharOperation; import org.eclipse.dltk.core.CompletionProposal; import org.eclipse.dltk.core.DLTKLanguageManager; import org.eclipse.dltk.core.ISourceModule; import org.eclipse.dltk.internal.javascript.ti.TypeInferencer2; import org.eclipse.dltk.javascript.core.JSKeywordCategory; import org.eclipse.dltk.javascript.core.JSKeywordManager; import org.eclipse.dltk.javascript.core.JavaScriptNature; import org.eclipse.dltk.javascript.internal.core.codeassist.JSCompletionEngine; import org.eclipse.dltk.javascript.internal.ui.JavaScriptUI; import org.eclipse.dltk.javascript.internal.ui.templates.JSDocTemplateCompletionProcessor; import org.eclipse.dltk.javascript.internal.ui.text.JSDocTextUtils; import org.eclipse.dltk.javascript.internal.ui.text.TypeNameNode; import org.eclipse.dltk.javascript.parser.jsdoc.JSDocTag; import org.eclipse.dltk.javascript.typeinfo.TypeMode; import org.eclipse.dltk.javascript.typeinfo.model.Type; import org.eclipse.dltk.javascript.typeinfo.model.TypeKind; import org.eclipse.dltk.ui.DLTKPluginImages; import org.eclipse.dltk.ui.templates.ScriptTemplateProposal; 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.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContextInformation; @SuppressWarnings("restriction") public class JSDocCompletionProposalComputer implements IScriptCompletionProposalComputer { public JSDocCompletionProposalComputer() { } public void sessionStarted() { } public void sessionEnded() { } public List<ICompletionProposal> computeCompletionProposals( ContentAssistInvocationContext context, IProgressMonitor monitor) { IDocument document = context.getDocument(); try { final IRegion region = JSDocTextUtils.getLineRegion(document, context.getInvocationOffset()); final char[] line = document.get(region.getOffset(), region.getLength()).toCharArray(); final int offsetInLine = context.getInvocationOffset() - region.getOffset(); final TypeNameNode tagName = JSDocTextUtils.getTag(line, 0, offsetInLine); if (tagName == null) { return Collections.emptyList(); } if (tagName.end == offsetInLine) { return completionOnTag(context, tagName.value()); } int index = tagName.end; while (index < offsetInLine && Character.isWhitespace(line[index])) { ++index; } if (JSDocTag.SEE.equals(tagName.value())) { int valueStart = index; while (index < offsetInLine) { if (Character.isWhitespace(line[index])) { return Collections.emptyList(); } ++index; } return completionAfterSee(context, new String(line, valueStart, index - valueStart)); } int depth = 0; int nameStart = index; boolean breakOnSpace = false; if (index < offsetInLine && line[index] == '{') { ++index; depth = 1; nameStart = index; } else if (JSDocTag.TYPE.equals(tagName.value())) { breakOnSpace = true; } else { return Collections.emptyList(); } while (index < offsetInLine) { if (line[index] == '}') { if (--depth <= 0) { return Collections.emptyList(); } } else if (line[index] == '{') { ++depth; } else if (line[index] == '<' || line[index] == '>' || line[index] == '(' || line[index] == ':' || line[index] == ',' || line[index] == '|') { nameStart = index + 1; } else if (breakOnSpace && Character.isWhitespace(line[index])) { return Collections.emptyList(); } ++index; } if (index == offsetInLine) { return completionOnType(context, new String(line, nameStart, index - nameStart)); } } catch (BadLocationException e) { JavaScriptUI.log(e); } return Collections.emptyList(); } private static final char JSDOC_MEMBER_SEPARATOR = '#'; /** * @param context * @param prefix * @return */ private List<ICompletionProposal> completionAfterSee( ContentAssistInvocationContext context, String prefix) { if (!(context instanceof ScriptContentAssistInvocationContext)) { return Collections.emptyList(); } int pos = prefix.indexOf(JSDOC_MEMBER_SEPARATOR); if (pos < 0) { return completionOnType(context, prefix); } else { final JSCompletionEngine engine = getCompletionEngine(); if (engine == null) { return Collections.emptyList(); } final ISourceModule module = ((ScriptContentAssistInvocationContext) context) .getSourceModule(); final JavaScriptCompletionProposalCollector collector = new JavaScriptCompletionProposalCollector( module); collector .setInvocationContext((ScriptContentAssistInvocationContext) context); engine.setRequestor(collector); collector.setAttribute(TypeMode.JSDOC, Boolean.TRUE); final String typeName = prefix.substring(0, pos); if (typeName.length() != 0) { final TypeInferencer2 inferencer2 = new TypeInferencer2(); inferencer2.setModelElement(module); final Type type = inferencer2.getType(typeName); if (type == null || type.getKind() == TypeKind.UNKNOWN) { return Collections.emptyList(); } // TODO (alex) generate proposals directly, without engine engine.completeMembers(module, prefix.substring(pos + 1), context.getInvocationOffset(), true, inferencer2 .convert(type).getMembers()); return Arrays.<ICompletionProposal> asList(collector .getScriptCompletionProposals()); } else { collector.setIgnored(CompletionProposal.KEYWORD, true); collector.setIgnored(CompletionProposal.TYPE_REF, true); engine.setGlobalOptions(JSCompletionEngine.OPTION_NONE); engine.completeGlobals(module, prefix.substring(1), context.getInvocationOffset(), true); return Arrays.<ICompletionProposal> asList(collector .getScriptCompletionProposals()); } } } private List<ICompletionProposal> completionOnTag( ContentAssistInvocationContext context, String tag) { final List<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); final JSDocTemplateCompletionProcessor processor = new JSDocTemplateCompletionProcessor( (ScriptContentAssistInvocationContext) context); Collections.addAll(proposals, processor.computeCompletionProposals( context.getViewer(), context.getInvocationOffset())); // final Set<String> tags = new HashSet<String>(); Collections.addAll(tags, JSDocTag.getTags()); ISourceModule module = context instanceof ScriptContentAssistInvocationContext ? ((ScriptContentAssistInvocationContext) context) .getSourceModule() : null; Collections.addAll( tags, JSKeywordManager.getInstance().getKeywords( JSKeywordCategory.JS_DOC_TAG, module)); // TODO option to ignore @returns ? tags.remove(JSDocTag.RETURNS); // collect used tags, to show keywords only for missing ones final Set<String> usedTags = new HashSet<String>(); for (ICompletionProposal proposal : proposals) { if (proposal instanceof ScriptTemplateProposal) { usedTags.add(((ScriptTemplateProposal) proposal) .getTemplateName()); } } for (String jsdocTag : tags) { if (CharOperation.prefixEquals(tag, jsdocTag) && !usedTags.contains(jsdocTag)) { proposals.add(new ScriptCompletionProposal(jsdocTag + ' ', context.getInvocationOffset() - tag.length(), tag .length(), DLTKPluginImages .get(DLTKPluginImages.IMG_OBJS_JAVADOCTAG), jsdocTag, 90, true)); } } return proposals; } /** * @param context * @param prefix * @return */ private List<ICompletionProposal> completionOnType( ContentAssistInvocationContext context, String prefix) { if (context instanceof ScriptContentAssistInvocationContext) { final JSCompletionEngine engine = getCompletionEngine(); if (engine != null) { final ISourceModule module = ((ScriptContentAssistInvocationContext) context) .getSourceModule(); final JavaScriptCompletionProposalCollector collector = new JavaScriptCompletionProposalCollector( module); collector.setAttribute(TypeMode.JSDOC, Boolean.TRUE); collector .setInvocationContext((ScriptContentAssistInvocationContext) context); engine.setRequestor(collector); engine.completeTypes(module, TypeMode.JSDOC, prefix.trim(), context.getInvocationOffset()); return Arrays.<ICompletionProposal> asList(collector .getScriptCompletionProposals()); } } return Collections.emptyList(); } private JSCompletionEngine getCompletionEngine() { final ICompletionEngine[] engines = DLTKLanguageManager .getCompletionEngines(JavaScriptNature.NATURE_ID); if (engines != null) { for (ICompletionEngine engine : engines) { if (engine instanceof JSCompletionEngine) { return (JSCompletionEngine) engine; } } } return null; } public List<IContextInformation> computeContextInformation( ContentAssistInvocationContext context, IProgressMonitor monitor) { return Collections.emptyList(); } public String getErrorMessage() { return null; } }