package com.baselet.plugin.contentAssist; import java.io.IOException; import java.io.InputStream; import java.util.ArrayList; import java.util.Collections; import java.util.List; import org.eclipse.core.resources.IContainer; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IFolder; import org.eclipse.core.resources.IResource; import org.eclipse.core.resources.IResourceVisitor; import org.eclipse.core.runtime.CoreException; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.Path; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.IMember; import org.eclipse.jdt.core.IPackageFragmentRoot; import org.eclipse.jdt.core.ISourceRange; import org.eclipse.jdt.core.JavaModelException; import org.eclipse.jdt.ui.text.java.ContentAssistInvocationContext; import org.eclipse.jdt.ui.text.java.IJavaCompletionProposalComputer; import org.eclipse.jdt.ui.text.java.JavaContentAssistInvocationContext; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.ICompletionProposalExtension4; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import org.eclipse.ui.IWorkbenchPage; import org.eclipse.ui.PlatformUI; import org.eclipse.ui.ide.IDE; import com.baselet.plugin.UmletPluginUtils; import com.baselet.plugin.refactoring.JavaDocParser; import com.baselet.plugin.refactoring.JavaDocParser.HtmlTagAttr; import com.baselet.plugin.refactoring.JavaDocParser.HtmlTagStartNode; import com.baselet.plugin.refactoring.JavaDocParser.JavaDocCommentNode; import com.baselet.plugin.refactoring.JavaDocParser.JavaDocNodeBase; import com.baselet.plugin.wizard.NewWizard; /** * Proposal computer for various proposals related to image references in javadoc comments. */ public class ImgRefProposalComputer implements IJavaCompletionProposalComputer { /** * Proposal replacing a piece of text */ private static class ReplacementProposal implements ICompletionProposal, ICompletionProposalExtension4 { private final int replacementOffset; private final int replacementLength; private final String displayString; private final String replacementString; private boolean autoInsertable = true; public ReplacementProposal(String displayString, String replacementString, int replacementOffset, int replacementLength) { this.displayString = displayString; this.replacementString = replacementString; this.replacementOffset = replacementOffset; this.replacementLength = replacementLength; } @Override public void apply(IDocument document) { try { document.replace(replacementOffset, replacementLength, replacementString); } catch (BadLocationException e) { throw new RuntimeException(e); } } @Override public Point getSelection(IDocument document) { return null; } @Override public String getAdditionalProposalInfo() { return null; } @Override public String getDisplayString() { return displayString; } @Override public Image getImage() { return null; } @Override public IContextInformation getContextInformation() { return null; } @Override public boolean isAutoInsertable() { return autoInsertable; } public ReplacementProposal setAutoInsertable(boolean autoInsertable) { this.autoInsertable = autoInsertable; return this; } } @Override public List<ICompletionProposal> computeCompletionProposals(ContentAssistInvocationContext context, IProgressMonitor monitor) { ArrayList<ICompletionProposal> proposals = new ArrayList<ICompletionProposal>(); System.out.println(context); if (context instanceof JavaContentAssistInvocationContext) { JavaContentAssistInvocationContext javaContext = (JavaContentAssistInvocationContext) context; try { if (UmletPluginUtils.hasUmletNature(javaContext.getProject())) { IDocument document = context.getDocument(); if (document != null) { computeCompletionProposals(javaContext, document, proposals); } } } catch (Exception e) { throw new RuntimeException(e); } } return proposals; } private void computeCompletionProposals(JavaContentAssistInvocationContext javaContext, IDocument document, ArrayList<ICompletionProposal> proposals) throws CoreException { String content = document.get(); int offset = javaContext.getInvocationOffset(); // try to get the javadoc of the element at the offset IJavaElement elementAt = javaContext.getCompilationUnit().getElementAt(offset); if (elementAt instanceof IMember) { ISourceRange range = ((IMember) elementAt).getJavadocRange(); if (range != null) { // parse javadoc JavaDocCommentNode comment = new JavaDocParser(content, range.getOffset(), range.getOffset() + range.getLength()).comment(); boolean inStartTag = false; // search the html tags for (JavaDocNodeBase child : comment.children) { if (child instanceof HtmlTagStartNode) { HtmlTagStartNode tag = (HtmlTagStartNode) child; if (tag.start <= offset && offset < tag.end) { // no prefix proposals within start tags inStartTag = true; if ("img".equals(tag.tagName.getValue())) { HtmlTagAttr srcAttr = tag.getAttr("src"); if (srcAttr != null) { addTransformBetweenAbsoluteAndRelativeLinkProposals(javaContext, proposals, srcAttr); if (srcAttr.value.start <= offset && offset <= srcAttr.value.end) { addChangeSrcToExistingResourceProposals(javaContext, proposals, srcAttr); } } } break; } } } if (!inStartTag) { int lastSpaceIndex = content.lastIndexOf(' ', offset - 1) + 1; // skip the leading /** int javadocContentStartOffset = range.getOffset() + 3; lastSpaceIndex = Math.max(lastSpaceIndex, javadocContentStartOffset); if (lastSpaceIndex < offset) { String prefix = content.substring(lastSpaceIndex, offset); addLinkToExistingResourceProposals(javaContext, proposals, prefix, lastSpaceIndex, offset - lastSpaceIndex); addCreateNewImageProposal(javaContext, proposals, prefix, lastSpaceIndex, offset - lastSpaceIndex); } } } } } private void addCreateNewImageProposal(final JavaContentAssistInvocationContext javaContext, ArrayList<ICompletionProposal> proposals, String prefix, int offset, int length) { final IContainer parent = UmletPluginUtils.getCompilationUnitParent(javaContext.getCompilationUnit()); if (parent == null) { return; } // no empty files if (prefix.length() == 0) { return; } final IFile uxfFile = parent.getFile(new Path("doc-files/" + prefix + ".uxf")); if (uxfFile.exists()) { return; } proposals.add(new ReplacementProposal("Create and link new Umlet diagram doc-files/" + prefix + ".uxf", "<img src=\"doc-files/" + prefix + ".png\" alt=\"\">", offset, length) { @Override public void apply(IDocument document) { super.apply(document); try { // create doc-files folder IFolder docFiles = parent.getFolder(new Path("doc-files")); if (!docFiles.exists()) { docFiles.create(true, true, null); } // create image file { InputStream stream = null; try { stream = NewWizard.openContentStream(); uxfFile.create(stream, true, null); stream.close(); } catch (IOException e) { // ignore } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { // swallow } } } } // open editor IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage(); IDE.openEditor(page, uxfFile, true); } catch (CoreException e) { throw new RuntimeException(e); } } }.setAutoInsertable(false)); } private void addChangeSrcToExistingResourceProposals(JavaContentAssistInvocationContext javaContext, ArrayList<ICompletionProposal> proposals, HtmlTagAttr srcAttr) throws CoreException { String src = srcAttr.value.getValue(); // add proposals for resource links { int prefixStart = src.lastIndexOf('/') + 1; int prefixEnd = javaContext.getInvocationOffset() - srcAttr.value.start; if (prefixEnd >= 0 && prefixStart < prefixEnd && prefixEnd <= src.length()) { String prefix = src.substring(prefixStart, prefixEnd); for (String path : collectResourcePaths(javaContext, prefix)) { // skip if no change would result if (src.equals(path)) { continue; } proposals.add(new ReplacementProposal("Change link to " + path, path, srcAttr.value.start, srcAttr.value.length())); } } } } private void addTransformBetweenAbsoluteAndRelativeLinkProposals(JavaContentAssistInvocationContext javaContext, ArrayList<ICompletionProposal> proposals, HtmlTagAttr srcAttr) throws JavaModelException { final IPath javaResourceParentPath = UmletPluginUtils.getCompilationUnitParentPath(javaContext.getCompilationUnit()); if (javaResourceParentPath == null) { return; } String src = srcAttr.value.getValue(); if (UmletPluginUtils.isAbsoluteImageRef(src)) { // propose to transform to relative Path path = new Path(src.substring("{@docRoot}".length())); String replacement = path.makeRelativeTo(javaResourceParentPath).toString(); proposals.add(new ReplacementProposal("Transform src to " + replacement, replacement, srcAttr.value.start, srcAttr.value.length()).setAutoInsertable(false)); } else { // propose to transform to absolute String replacement = "{@docRoot}/" + javaResourceParentPath.append(src).toString(); proposals.add(new ReplacementProposal("Transform src to " + replacement, replacement, srcAttr.value.start, srcAttr.value.length()).setAutoInsertable(false)); } } private List<String> collectResourcePaths(final JavaContentAssistInvocationContext context, final String prefix) throws CoreException { final ArrayList<String> result = new ArrayList<String>(); final IPath javaResourceParentPath = UmletPluginUtils.getCompilationUnitParentPath(context.getCompilationUnit()); if (javaResourceParentPath == null) { return result; } IPackageFragmentRoot root = UmletPluginUtils.getPackageFragmentRoot(context.getCompilationUnit()); final IResource rootResource = root.getCorrespondingResource(); if (rootResource == null) { return result; } rootResource.accept(new IResourceVisitor() { @Override public boolean visit(IResource uxfResource) throws CoreException { if (!uxfResource.isAccessible()) { return false; } if (uxfResource instanceof IFile) { if (uxfResource.getName().endsWith(".uxf") && uxfResource.getName().toLowerCase().contains(prefix.toLowerCase())) { IFile imgFile = UmletPluginUtils.getImageForUxfPath((IFile) uxfResource); IPath imgPath = imgFile.getProjectRelativePath().makeRelativeTo(rootResource.getProjectRelativePath()); result.add(UmletPluginUtils.calculateImageRef(javaResourceParentPath, imgPath)); } } return true; } }); return result; } private void addLinkToExistingResourceProposals(final JavaContentAssistInvocationContext context, final ArrayList<ICompletionProposal> proposals, final String prefix, final int inputOffset, final int inputLength) throws CoreException { List<String> collectResourcePaths = collectResourcePaths(context, prefix); for (int i = 0; i < collectResourcePaths.size() && i < 50; i++) { String path = collectResourcePaths.get(i); proposals.add(new ReplacementProposal("Link to " + path, "<img src=\"" + path + "\" alt=\"\">", inputOffset, inputLength)); } } @Override public List<IContextInformation> computeContextInformation(ContentAssistInvocationContext context, IProgressMonitor monitor) { return Collections.emptyList(); } @Override public String getErrorMessage() { return null; } @Override public void sessionStarted() {} @Override public void sessionEnded() {} }