/** * Aptana Studio * Copyright (c) 2005-2011 by Appcelerator, Inc. All Rights Reserved. * Licensed under the terms of the Eclipse Public License (EPL). * Please see the license-epl.html included with this distribution for details. * Any modifications to this file must keep this entire header intact. */ package com.aptana.editor.php.internal.contentAssist; import java.util.List; import java.util.Map; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.contentassist.ICompletionProposal; import org.eclipse.jface.text.contentassist.IContextInformation; import org.eclipse.jface.text.link.ILinkedModeListener; import org.eclipse.jface.text.link.InclusivePositionUpdater; import org.eclipse.jface.text.link.LinkedModeModel; import org.eclipse.jface.text.link.LinkedModeUI; import org.eclipse.jface.text.link.LinkedPosition; import org.eclipse.jface.text.link.LinkedPositionGroup; import org.eclipse.swt.graphics.Image; import org.eclipse.swt.graphics.Point; import com.aptana.editor.common.contentassist.CommonCompletionProposal; /** * PHP code assist completion proposal. * * @author Shalom Gibly <sgibly@aptana.com>, Pavel Petrochenko */ public class PHPCompletionProposal extends CommonCompletionProposal { /** The type of the object. This is an 'enum' of JSCompletionProposalComparator sorting types. */ private int fObjectType; private IRegion fSelectedRegion; // initialized by apply() /** * Position updater. */ private InclusivePositionUpdater fUpdater; private IDocumentationResolver resolver; /** * Viewer. */ protected ITextViewer viewer; /** * Proposal positions list. */ private List<Position> positions; /** * Caret exit offset. */ private int exitCaretOffset; /** * Creates a new completion proposal. All fields are initialized based on the provided information. * * @param replacementString * the actual string to be inserted into the document * @param replacementOffset * the offset of the text to be replaced * @param replacementLength * the length of the text to be replaced * @param cursorPosition * the position of the cursor following the insert relative to replacementOffset * @param image * the image to display for this proposal * @param displayString * the string to be displayed for the proposal * @param contextInformation * the context information associated with this proposal * @param additionalProposalInfo * the additional information associated with this proposal * @param objectType * The type of the object. This is an 'enum' of JSCompletionProposalComparator sorting types (used for * quicker sorting). * @param fileLocation * The source file location where the CA proposal was found. * @param userAgentImages */ public PHPCompletionProposal(String replacementString, int replacementOffset, int replacementLength, int cursorPosition, Image image, String displayString, IContextInformation contextInformation, String additionalProposalInfo, int objectType, String fileLocation, Map<String, String> userAgentImages) { super(replacementString, replacementOffset, replacementLength, cursorPosition, image, displayString, contextInformation, additionalProposalInfo); fObjectType = objectType; setFileLocation(fileLocation); setUserAgentImages(userAgentImages); } /** * padToColumn * * @param stringToPad * @param columnWidth * @return String */ public static String padToColumn(String stringToPad, int columnWidth) { String blanks = " "; //$NON-NLS-1$ if (stringToPad.length() > columnWidth) { return stringToPad.substring(0, columnWidth); } else { int blankLength = columnWidth - stringToPad.length(); return stringToPad + blanks.substring(0, blankLength); } } /** * Returns the type of object this proposal contains (class, method, etc).This is used for sorting. * * @return Returns the type of object this proposal contains (class, method, etc).This is used for sorting. */ public int getObjectType() { return fObjectType; } /** * Override the common validation for the PHP proposals since the display string can contain extra information, such * as '(local)' string, etc. * * @see org.eclipse.jface.text.contentassist.ICompletionProposalExtension2#validate(org.eclipse.jface.text.IDocument, * int, org.eclipse.jface.text.DocumentEvent) */ public boolean validate(IDocument document, int offset, DocumentEvent event) { if (offset < this._replacementOffset) { return false; } String prefix = getPrefix(document, offset); return validate(prefix, event); } public boolean validate(String prefix, DocumentEvent event) { String proposalContent = null; if (resolver != null) { proposalContent = resolver.getProposalContent(); } if (proposalContent == null) { proposalContent = getDisplayString(); } int overlapIndex = proposalContent.length() - _replacementString.length(); overlapIndex = Math.max(0, overlapIndex); String endPortion = proposalContent.substring(overlapIndex); boolean validated = isValidPrefix(prefix, endPortion); if (validated && event != null) { // make sure that we change the replacement length as the document content changes int delta = ((event.fText == null) ? 0 : event.fText.length()) - event.fLength; final int newLength = Math.max(_replacementLength + delta, 0); _replacementLength = newLength; } return validated; } /* * (non-Javadoc) * @see com.aptana.editor.common.contentassist.CommonCompletionProposal#apply(org.eclipse.jface.text.ITextViewer, * char, int, int) */ @SuppressWarnings("unused") @Override public void apply(ITextViewer viewer, char trigger, int stateMask, int offset) { IDocument document = viewer.getDocument(); try { document.replace(_replacementOffset, _replacementLength+prefixReplaceLength, _replacementString); if (viewer != null) { LinkedModeModel model = new LinkedModeModel(); boolean positionsAdded = false; ensurePositionCategoryInstalled(document, model); if (positions != null && positions.size() != 0) { for (Position pos : positions) { try { document.addPosition(getCategory(), pos); LinkedPositionGroup group = new LinkedPositionGroup(); group.addPosition(new LinkedPosition(document, pos.offset, pos.length)); model.addGroup(group); positionsAdded = true; } catch (BadPositionCategoryException e) { ensurePositionCategoryRemoved(document); return; } } } if (positionsAdded) { model.forceInstall(); LinkedModeUI ui = new LinkedModeUI(model, viewer); ui.setExitPosition(viewer, exitCaretOffset, 0, Integer.MAX_VALUE); ui.enter(); fSelectedRegion = ui.getSelectedRegion(); } else { ensurePositionCategoryRemoved(document); } } else { ensurePositionCategoryRemoved(document); } } catch (BadLocationException x) { ensurePositionCategoryRemoved(document); } } /** * Sets viewer. * * @param viewer * - viewer. */ public void setViewer(ITextViewer viewer) { this.viewer = viewer; } /* * @see ICompletionProposal#getSelection(IDocument) */ public Point getSelection(IDocument document) { if (fSelectedRegion == null) return new Point(_replacementOffset, 0); return new Point(fSelectedRegion.getOffset(), fSelectedRegion.getLength()); } /** * @see ICompletionProposal#getAdditionalProposalInfo() */ public String getAdditionalProposalInfo() { if (resolver != null) { return resolver.resolveDocumentation(); } return super.getAdditionalProposalInfo(); } /** * @return resolver */ public IDocumentationResolver getResolver() { return resolver; } /** * sets resolver * * @param resolver */ public void setResolver(IDocumentationResolver resolver) { this.resolver = resolver; } /** * Set up proposal position structure. * * @param positions * - positions. * @param exitCaretOffset * - caret exit offset. */ public void setPositions(List<Position> positions, int exitCaretOffset) { this.positions = positions; this.exitCaretOffset = exitCaretOffset; } /** * Ensures that position category is installed to the document. * * @param document * - document. * @param model * - linked model. */ private void ensurePositionCategoryInstalled(final IDocument document, LinkedModeModel model) { if (!document.containsPositionCategory(getCategory())) { document.addPositionCategory(getCategory()); fUpdater = new InclusivePositionUpdater(getCategory()); document.addPositionUpdater(fUpdater); model.addLinkingListener(new ILinkedModeListener() { /* * @see * org.eclipse.jface.text.link.ILinkedModeListener#left(org.eclipse.jface.text.link.LinkedModeModel, * int) */ public void left(LinkedModeModel environment, int flags) { ensurePositionCategoryRemoved(document); } public void suspend(LinkedModeModel environment) { } public void resume(LinkedModeModel environment, int flags) { } }); } } /** * Ensures that position category is removed from the document. * * @param document * - document. */ private void ensurePositionCategoryRemoved(IDocument document) { if (document.containsPositionCategory(getCategory())) { try { document.removePositionCategory(getCategory()); } catch (BadPositionCategoryException e) // $codepro.audit.disable emptyCatchClause { // ignore } document.removePositionUpdater(fUpdater); } fSelectedRegion = new Region(_replacementOffset + _replacementString.length(), 0); } private String getCategory() { return "PHPProposalCategory_" + toString(); //$NON-NLS-1$ } public int getReplacementLength() { return _replacementLength; } public String getReplacementString() { return _replacementString; } public void setReplacementLength(int replacementLength) { _replacementLength = replacementLength; } /** * A simple strings comparison of completion proposals. */ public int compareTo(PHPCompletionProposal otherProposal) { String replacement = this.getReplacementString(); String otherReplacement = otherProposal.getReplacementString(); if (replacement.startsWith(otherReplacement)) { // Give this replacement priority as the shorter one return 1; } return replacement.compareTo(otherReplacement); } /* * (non-Javadoc) * @see java.lang.Object#toString() */ @Override public String toString() { StringBuilder builder = new StringBuilder(); builder.append("{PHPCompletionProposal"); //$NON-NLS-1$ builder.append(", Replacement: "); //$NON-NLS-1$ builder.append(getReplacementString()); builder.append(", Display: "); //$NON-NLS-1$ builder.append(getDisplayString()); builder.append(", Relevance: "); //$NON-NLS-1$ builder.append(getRelevance()); builder.append('}'); return builder.toString(); } }