| package org.eclipse.dltk.ui.text.folding; |
| |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.IModelElementVisitor; |
| import org.eclipse.dltk.core.ISourceModule; |
| import org.eclipse.dltk.core.ISourceRange; |
| import org.eclipse.dltk.core.ISourceReference; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.internal.core.SourceType; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.Document; |
| |
| public class DefaultElementCommentResolver implements IElementCommentResolver { |
| |
| private final ISourceModule fModule; |
| private final String fContent; |
| private Document fDocument = null; |
| |
| public DefaultElementCommentResolver(ISourceModule module) { |
| this(module, null); |
| } |
| |
| /** |
| * @param modelElement |
| * @param contents |
| */ |
| public DefaultElementCommentResolver(ISourceModule module, String contents) { |
| this.fModule = module; |
| this.fContent = contents; |
| } |
| |
| /** |
| * Determines the element that contains the clicked comment |
| * |
| * @throws ModelException |
| */ |
| protected IModelElement getContainingElement(IModelElement el, int offset, |
| int length) throws ModelException { |
| final PositionVisitor visitor = new PositionVisitor(offset, length); |
| el.accept(visitor); |
| return visitor.result; |
| } |
| |
| /** |
| * Returns the model element that the comment corresponds to |
| */ |
| @Override |
| public IModelElement getElementByCommentPosition(int offset, int length) { |
| try { |
| return getElementByCommentPositionImpl(offset, length); |
| } catch (BadLocationException e1) { |
| return null; |
| } catch (ModelException e) { |
| return null; |
| } |
| } |
| |
| protected IModelElement getElementByCommentPositionImpl(int offset, |
| int length) throws BadLocationException, ModelException { |
| |
| if (fDocument == null) { |
| fDocument = new Document(getSource()); |
| } |
| |
| // Determine that the desired position is inside a comment |
| if (!checkIfPositionIsComment(fDocument, offset)) |
| return null; |
| |
| // Determine the innermost element that contains the clicked comment |
| // (for example, class declaration) |
| IModelElement el = getContainingElement(fModule, offset, length); |
| |
| // If the comment is inside a method, we do not need to process further |
| if (el != null && el.getElementType() == IModelElement.METHOD) |
| return el; |
| |
| // Determine the position after which the search will be stopped - for |
| // example, EOF or end of the class declaration |
| int sourceRangeEnd = getSourceRangeEnd(fDocument, el); |
| |
| // Search for first non-comment element after the clicked comment |
| IModelElement res = searchForNonCommentElement(fDocument, fModule, |
| offset + length, sourceRangeEnd); |
| if (res == null) |
| return el; |
| return res; |
| } |
| |
| /** |
| * @return |
| * @throws ModelException |
| */ |
| protected String getSource() throws ModelException { |
| return fContent != null ? fContent : fModule.getSource(); |
| } |
| |
| protected int getSourceRangeEnd(Document d, IModelElement el) |
| throws ModelException { |
| |
| int sourceRangeEnd = d.getLength(); |
| |
| // If the comment is inside a class, we need to stop searching the |
| // element once we leave the class boundaries |
| if (el != null && el.getElementType() == IModelElement.TYPE) { |
| SourceType t = (SourceType) el; |
| sourceRangeEnd = t.getSourceRange().getOffset() |
| + t.getSourceRange().getLength(); |
| |
| } |
| return sourceRangeEnd; |
| } |
| |
| protected boolean checkIfPositionIsComment(Document d, int offset) |
| throws BadLocationException { |
| int line = d.getLineOfOffset(offset); |
| int q = d.getLineOffset(line); |
| |
| while (q < d.getLength() && Character.isWhitespace(d.getChar(q)) |
| && q <= offset) { |
| q++; |
| } |
| |
| if (d.getChar(q) != '#') { |
| /* First non-space char is not a comment start, so stop processing */ |
| return false; |
| } |
| |
| return true; |
| } |
| |
| protected IModelElement searchForNonCommentElement(Document d, |
| ISourceModule content, int endOfCommentOffset, int lowerbound) |
| throws BadLocationException, ModelException { |
| IModelElement res = null; |
| int off = endOfCommentOffset; |
| int line = d.getLineOfOffset(off); |
| off = d.getLineOffset(line); |
| while (off < lowerbound) { |
| |
| while (off < lowerbound - 1 |
| && Character.isWhitespace(d.getChar(off))) { |
| off++; |
| } |
| |
| if (d.getChar(off) != '#') { |
| // It's neither a comment nor whitespace, so we can get the |
| // model element at this position |
| res = content.getElementAt(off); |
| break; |
| } |
| line++; |
| off = d.getLineOffset(line); |
| |
| } |
| |
| return res; |
| } |
| |
| /** |
| * Visitor to search the AST for elements that contain the clicked comment |
| */ |
| private static class PositionVisitor implements IModelElementVisitor { |
| |
| IModelElement result = null; |
| private final int offset; |
| private final int length; |
| |
| public PositionVisitor(int offset, int length) { |
| this.offset = offset; |
| this.length = length; |
| } |
| |
| @Override |
| public boolean visit(IModelElement element) { |
| if (element instanceof ISourceReference) { |
| final ISourceRange range; |
| try { |
| range = ((ISourceReference) element).getSourceRange(); |
| } catch (ModelException e) { |
| return true; |
| } |
| // The comment is entirely inside the element |
| if (offset >= range.getOffset() |
| && offset + length <= range.getOffset() |
| + range.getLength()) { |
| if (element.getElementType() == IModelElement.METHOD |
| || element.getElementType() == IModelElement.TYPE) |
| result = element; |
| } |
| } |
| |
| return true; |
| } |
| |
| } |
| |
| } |