| /******************************************************************************* |
| * Copyright (c) 2005, 2018 IBM Corporation 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 |
| * |
| *******************************************************************************/ |
| package org.eclipse.dltk.ui.text.completion; |
| |
| import java.io.BufferedReader; |
| import java.io.IOException; |
| import java.io.InputStreamReader; |
| import java.net.URL; |
| |
| import org.eclipse.core.runtime.Assert; |
| import org.eclipse.core.runtime.FileLocator; |
| import org.eclipse.core.runtime.IProgressMonitor; |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.NullProgressMonitor; |
| import org.eclipse.core.runtime.Platform; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.dltk.compiler.CharOperation; |
| import org.eclipse.dltk.core.DLTKCore; |
| import org.eclipse.dltk.core.IModelElement; |
| import org.eclipse.dltk.core.ModelException; |
| import org.eclipse.dltk.core.search.SearchPattern; |
| import org.eclipse.dltk.internal.corext.util.Strings; |
| import org.eclipse.dltk.internal.ui.text.hover.DocumentationHover; |
| import org.eclipse.dltk.ui.DLTKUIPlugin; |
| import org.eclipse.dltk.ui.PreferenceConstants; |
| import org.eclipse.dltk.ui.text.ScriptTextTools; |
| import org.eclipse.jface.internal.text.html.BrowserInformationControl; |
| import org.eclipse.jface.preference.IPreferenceStore; |
| import org.eclipse.jface.preference.PreferenceConverter; |
| import org.eclipse.jface.resource.JFaceResources; |
| import org.eclipse.jface.text.BadLocationException; |
| import org.eclipse.jface.text.BadPositionCategoryException; |
| import org.eclipse.jface.text.DefaultPositionUpdater; |
| import org.eclipse.jface.text.DocumentEvent; |
| import org.eclipse.jface.text.IDocument; |
| import org.eclipse.jface.text.IInformationControlCreator; |
| import org.eclipse.jface.text.IPositionUpdater; |
| import org.eclipse.jface.text.IRegion; |
| import org.eclipse.jface.text.ITextViewer; |
| import org.eclipse.jface.text.ITextViewerExtension2; |
| import org.eclipse.jface.text.ITextViewerExtension5; |
| import org.eclipse.jface.text.Position; |
| import org.eclipse.jface.text.Region; |
| import org.eclipse.jface.text.contentassist.BoldStylerProvider; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension2; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension3; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension5; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension6; |
| import org.eclipse.jface.text.contentassist.ICompletionProposalExtension7; |
| import org.eclipse.jface.text.contentassist.IContextInformation; |
| import org.eclipse.jface.text.link.ILinkedModeListener; |
| import org.eclipse.jface.text.link.LinkedModeModel; |
| import org.eclipse.jface.text.link.LinkedModeUI; |
| import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags; |
| import org.eclipse.jface.text.link.LinkedModeUI.IExitPolicy; |
| import org.eclipse.jface.text.link.LinkedPosition; |
| import org.eclipse.jface.text.link.LinkedPositionGroup; |
| import org.eclipse.jface.viewers.StyledString; |
| import org.eclipse.jface.viewers.StyledString.Styler; |
| import org.eclipse.osgi.util.TextProcessor; |
| import org.eclipse.swt.SWT; |
| import org.eclipse.swt.custom.StyleRange; |
| import org.eclipse.swt.custom.StyledText; |
| import org.eclipse.swt.events.VerifyEvent; |
| import org.eclipse.swt.graphics.Color; |
| import org.eclipse.swt.graphics.FontData; |
| import org.eclipse.swt.graphics.Image; |
| import org.eclipse.swt.graphics.Point; |
| import org.eclipse.swt.graphics.RGB; |
| import org.eclipse.swt.graphics.TextStyle; |
| import org.eclipse.swt.widgets.Shell; |
| import org.eclipse.ui.IWorkbenchPage; |
| import org.eclipse.ui.IWorkbenchPart; |
| import org.eclipse.ui.IWorkbenchSite; |
| import org.eclipse.ui.texteditor.link.EditorLinkedModeUI; |
| import org.osgi.framework.Bundle; |
| |
| @SuppressWarnings("restriction") |
| public abstract class AbstractScriptCompletionProposal |
| implements IScriptCompletionProposal, ICompletionProposalExtension, |
| ICompletionProposalExtension2, ICompletionProposalExtension3, |
| ICompletionProposalExtension5, ICompletionProposalExtension6, |
| ICompletionProposalExtension7 { |
| |
| /** |
| * A class to simplify tracking a reference position in a document. |
| */ |
| static final class ReferenceTracker { |
| |
| /** The reference position category name. */ |
| private static final String CATEGORY = "reference_position"; //$NON-NLS-1$ |
| /** The position updater of the reference position. */ |
| private final IPositionUpdater fPositionUpdater = new DefaultPositionUpdater( |
| CATEGORY); |
| /** The reference position. */ |
| private final Position fPosition = new Position(0); |
| |
| /** |
| * Called before document changes occur. It must be followed by a call |
| * to postReplace(). |
| * |
| * @param document |
| * the document on which to track the reference |
| * position. |
| * |
| */ |
| public void preReplace(IDocument document, int offset) |
| throws BadLocationException { |
| fPosition.setOffset(offset); |
| try { |
| document.addPositionCategory(CATEGORY); |
| document.addPositionUpdater(fPositionUpdater); |
| document.addPosition(CATEGORY, fPosition); |
| |
| } catch (BadPositionCategoryException e) { |
| // should not happen |
| DLTKUIPlugin.log(e); |
| } |
| } |
| |
| /** |
| * Called after the document changed occurred. It must be preceded by a |
| * call to preReplace(). |
| * |
| * @param document |
| * the document on which to track the reference |
| * position. |
| */ |
| public int postReplace(IDocument document) { |
| try { |
| document.removePosition(CATEGORY, fPosition); |
| document.removePositionUpdater(fPositionUpdater); |
| document.removePositionCategory(CATEGORY); |
| |
| } catch (BadPositionCategoryException e) { |
| // should not happen |
| DLTKUIPlugin.log(e); |
| } |
| return fPosition.getOffset(); |
| } |
| } |
| |
| protected static class ExitPolicy implements IExitPolicy { |
| |
| final char fExitCharacter; |
| private final IDocument fDocument; |
| |
| public ExitPolicy(char exitCharacter, IDocument document) { |
| fExitCharacter = exitCharacter; |
| fDocument = document; |
| } |
| |
| @Override |
| public ExitFlags doExit(LinkedModeModel environment, VerifyEvent event, |
| int offset, int length) { |
| |
| if (event.character == fExitCharacter) { |
| if (environment.anyPositionContains(offset)) { |
| return new ExitFlags(ILinkedModeListener.UPDATE_CARET, |
| false); |
| } |
| return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true); |
| } |
| |
| switch (event.character) { |
| case ';': |
| return new ExitFlags(ILinkedModeListener.NONE, true); |
| case SWT.CR: |
| // when entering an anonymous class as a parameter, we don't |
| // want |
| // to jump after the parenthesis when return is pressed |
| if (offset > 0) { |
| try { |
| if (fDocument.getChar(offset - 1) == '{') |
| return new ExitFlags(ILinkedModeListener.EXIT_ALL, |
| true); |
| } catch (BadLocationException e) { |
| } |
| } |
| // fall through |
| default: |
| return null; |
| } |
| } |
| |
| } |
| |
| private StyledString fDisplayString; |
| private String fReplacementString; |
| private int fReplacementOffset; |
| private int fReplacementLength; |
| private int fCursorPosition; |
| private Image fImage; |
| private IContextInformation fContextInformation; |
| private ICompletionProposalInfo fProposalInfo; |
| private char[] fTriggerCharacters; |
| private String fSortString; |
| private int fRelevance; |
| private boolean fIsInDoc; |
| private int fPatternMatchRule = -1; |
| |
| private StyleRange fRememberedStyleRange; |
| private boolean fToggleEating; |
| private ITextViewer fTextViewer; |
| |
| /** |
| * The control creator. |
| */ |
| private IInformationControlCreator fCreator; |
| |
| /** |
| * The style sheet (css). |
| */ |
| private static String fgCSSStyles; |
| |
| protected AbstractScriptCompletionProposal() { |
| } |
| |
| @Override |
| public char[] getTriggerCharacters() { |
| return fTriggerCharacters; |
| } |
| |
| /** |
| * Sets the trigger characters. |
| * |
| * @param triggerCharacters |
| * The set of characters which can trigger the |
| * application of this completion proposal |
| */ |
| public void setTriggerCharacters(char[] triggerCharacters) { |
| fTriggerCharacters = triggerCharacters; |
| } |
| |
| /** |
| * Sets the proposal info. |
| * |
| * @param proposalInfo |
| * The additional information associated with this |
| * proposal or <code>null</code> |
| */ |
| public void setProposalInfo(ICompletionProposalInfo proposalInfo) { |
| fProposalInfo = proposalInfo; |
| } |
| |
| /** |
| * Returns the additional proposal info, or <code>null</code> if none |
| * exists. |
| * |
| * @return the additional proposal info, or <code>null</code> if none exists |
| */ |
| public ICompletionProposalInfo getProposalInfo() { |
| return fProposalInfo; |
| } |
| |
| /** |
| * Sets the cursor position relative to the insertion offset. By default |
| * this is the length of the completion string (Cursor positioned after the |
| * completion) |
| * |
| * @param cursorPosition |
| * The cursorPosition to set |
| */ |
| public void setCursorPosition(int cursorPosition) { |
| Assert.isTrue(cursorPosition >= 0); |
| fCursorPosition = cursorPosition; |
| } |
| |
| protected int getCursorPosition() { |
| return fCursorPosition; |
| } |
| |
| @Override |
| public final void apply(IDocument document) { |
| // not used any longer |
| apply(document, (char) 0, |
| getReplacementOffset() + getReplacementLength()); |
| } |
| |
| @Override |
| public void apply(IDocument document, char trigger, int offset) { |
| try { |
| // patch replacement length |
| int delta = offset |
| - (getReplacementOffset() + getReplacementLength()); |
| if (delta > 0) |
| setReplacementLength(getReplacementLength() + delta); |
| |
| boolean isSmartTrigger = isSmartTrigger(trigger); |
| |
| if (!isSmartTrigger && trigger != (char) 0) { |
| ensureTriggerAtCursorLocation(trigger); |
| } |
| String replacement = getReplacementString(); |
| |
| // reference position just at the end of the document change. |
| int referenceOffset = getReplacementOffset() |
| + getReplacementLength(); |
| final ReferenceTracker referenceTracker = new ReferenceTracker(); |
| referenceTracker.preReplace(document, referenceOffset); |
| |
| replace(document, getReplacementOffset(), getReplacementLength(), |
| replacement); |
| postReplace(document); |
| |
| referenceOffset = referenceTracker.postReplace(document); |
| setReplacementOffset(referenceOffset |
| - (replacement == null ? 0 : replacement.length())); |
| |
| // PR 47097 |
| if (isSmartTrigger) |
| handleSmartTrigger(document, trigger, referenceOffset); |
| |
| } catch (BadLocationException x) { |
| // ignore |
| } |
| } |
| |
| protected void postReplace(IDocument document) throws BadLocationException { |
| } |
| |
| /** |
| * @since 3.0 |
| */ |
| protected void ensureTriggerAtCursorLocation(char trigger) { |
| String replacement = getReplacementString(); |
| |
| // fix for PR #5533. Assumes that no eating takes place. |
| if (getCursorPosition() > 0 |
| && getCursorPosition() <= replacement.length() |
| && replacement.charAt(getCursorPosition() - 1) != trigger) { |
| StringBuilder buffer = new StringBuilder(replacement); |
| buffer.insert(getCursorPosition(), trigger); |
| setReplacementString(buffer.toString()); |
| setCursorPosition(getCursorPosition() + 1); |
| } |
| } |
| |
| protected boolean isSmartTrigger(char trigger) { |
| return false; |
| } |
| |
| /** |
| * @param document |
| * @param trigger |
| * @param referenceOffset |
| * @throws BadLocationException |
| */ |
| protected void handleSmartTrigger(IDocument document, char trigger, |
| int referenceOffset) throws BadLocationException { |
| } |
| |
| protected final void replace(IDocument document, int offset, int length, |
| String string) throws BadLocationException { |
| if (!document.get(offset, length).equals(string)) |
| document.replace(offset, length, string); |
| } |
| |
| @Override |
| public void apply(ITextViewer viewer, char trigger, int stateMask, |
| int offset) { |
| |
| IDocument document = viewer.getDocument(); |
| if (fTextViewer == null) |
| fTextViewer = viewer; |
| |
| // see https://bugs.eclipse.org/bugs/show_bug.cgi?id=96059 |
| // don't apply the proposal if for some reason we're not valid any |
| // longer |
| if (!isInDoc() && !validate(document, offset, null)) { |
| setCursorPosition(offset - getReplacementOffset()); |
| if (trigger != '\0') { |
| try { |
| document.replace(offset, 0, String.valueOf(trigger)); |
| setCursorPosition(getCursorPosition() + 1); |
| applyAutoClose(trigger, document); |
| } catch (BadLocationException x) { |
| // ignore |
| } |
| } |
| return; |
| } |
| |
| // don't eat if not in preferences, XOR with modifier key 1 (Ctrl) |
| // but: if there is a selection, replace it! |
| Point selection = viewer.getSelectedRange(); |
| fToggleEating = (stateMask & SWT.MOD1) != 0; |
| int newLength = selection.x + selection.y - getReplacementOffset(); |
| if ((insertCompletion() ^ fToggleEating) && newLength >= 0) |
| setReplacementLength(newLength); |
| |
| apply(document, trigger, offset); |
| fToggleEating = false; |
| } |
| |
| protected void applyAutoClose(char trigger, IDocument document) |
| throws BadLocationException { |
| if (trigger == '(' && autocloseBrackets()) { |
| document.replace(getReplacementOffset() + getCursorPosition(), 0, |
| ")"); //$NON-NLS-1$ |
| setUpLinkedMode(document, ')'); |
| } |
| } |
| |
| /** |
| * Returns <code>true</code> if the proposal is within javadoc, |
| * <code>false</code> otherwise. |
| * |
| * @return <code>true</code> if the proposal is within javadoc, |
| * <code>false</code> otherwise |
| */ |
| @Deprecated |
| protected final boolean isInScriptdoc() { |
| return isInDoc(); |
| } |
| |
| /** |
| * Returns <code>true</code> if the proposal is within javadoc, |
| * <code>false</code> otherwise. |
| * |
| * @return <code>true</code> if the proposal is within javadoc, |
| * <code>false</code> otherwise |
| */ |
| protected boolean isInDoc() { |
| return fIsInDoc; |
| } |
| |
| /** |
| * Sets the javadoc attribute. |
| * |
| * @param isInDoc |
| * <code>true</code> if the proposal is within javadoc |
| */ |
| protected void setInDoc(boolean isInDoc) { |
| fIsInDoc = isInDoc; |
| } |
| |
| @Override |
| public Point getSelection(IDocument document) { |
| return new Point(getReplacementOffset() + getCursorPosition(), 0); |
| } |
| |
| @Override |
| public IContextInformation getContextInformation() { |
| return fContextInformation; |
| } |
| |
| /** |
| * Sets the context information. |
| * |
| * @param contextInformation |
| * The context information associated with |
| * this proposal |
| */ |
| public void setContextInformation(IContextInformation contextInformation) { |
| fContextInformation = contextInformation; |
| } |
| |
| @Override |
| public String getDisplayString() { |
| if (fDisplayString != null) |
| return fDisplayString.toString(); |
| return null; |
| } |
| |
| /** |
| * @since 5.2 |
| */ |
| @Override |
| public StyledString getStyledDisplayString() { |
| return fDisplayString; |
| } |
| |
| /** |
| * @since 5.2 |
| */ |
| public void setStyledDisplayString(StyledString text) { |
| fDisplayString = text; |
| } |
| |
| @Override |
| public StyledString getStyledDisplayString(IDocument document, int offset, |
| BoldStylerProvider boldStylerProvider) { |
| StyledString styledDisplayString = new StyledString(); |
| styledDisplayString.append(getStyledDisplayString()); |
| |
| String pattern = getPatternToEmphasizeMatch(document, offset); |
| if (pattern != null && pattern.length() > 0) { |
| String displayString = styledDisplayString.getString(); |
| boolean isJavadocTag = isInDoc() && displayString.charAt(0) == '@' |
| && pattern.charAt(0) == '@'; |
| if (isJavadocTag) { |
| displayString = displayString.substring(1); |
| pattern = pattern.substring(1); |
| } |
| int patternMatchRule = getPatternMatchRule(pattern, displayString); |
| int[] matchingRegions = SearchPattern.getMatchingRegions(pattern, |
| displayString, patternMatchRule); |
| if (isJavadocTag && matchingRegions != null) { |
| Strings.markMatchingRegions(styledDisplayString, 0, |
| new int[] { 0, 1 }, boldStylerProvider.getBoldStyler()); |
| for (int i = 0; i < matchingRegions.length; i += 2) { |
| matchingRegions[i]++; |
| } |
| } |
| Strings.markMatchingRegions(styledDisplayString, 0, matchingRegions, |
| boldStylerProvider.getBoldStyler()); |
| } |
| return styledDisplayString; |
| } |
| |
| protected int getPatternMatchRule(String pattern, String string) { |
| String start; |
| try { |
| start = string.substring(0, pattern.length()); |
| } catch (StringIndexOutOfBoundsException e) { |
| String message = "Error retrieving proposal text.\nDisplay string:\n" //$NON-NLS-1$ |
| + string + "\nPattern:\n" + pattern; //$NON-NLS-1$ |
| DLTKUIPlugin.log(new Status(IStatus.ERROR, |
| DLTKUIPlugin.getPluginId(), IStatus.OK, message, e)); |
| return -1; |
| } |
| if (start.equalsIgnoreCase(pattern)) { |
| return SearchPattern.R_PREFIX_MATCH; |
| } else if (isCamelCaseMatching() && CharOperation |
| .camelCaseMatch(pattern.toCharArray(), string.toCharArray())) { |
| return SearchPattern.R_CAMELCASE_MATCH; |
| } else if (isSubstringMatching() && CharOperation |
| .substringMatch(pattern.toCharArray(), string.toCharArray())) { |
| return SearchPattern.R_SUBSTRING_MATCH; |
| } else { |
| return -1; |
| } |
| } |
| |
| protected String getPatternToEmphasizeMatch(IDocument document, |
| int offset) { |
| int start = getPrefixCompletionStart(document, offset); |
| int patternLength = offset - start; |
| String pattern = null; |
| try { |
| pattern = document.get(start, patternLength); |
| } catch (BadLocationException e) { |
| // return null |
| } |
| return pattern; |
| } |
| |
| /** |
| * Returns true if substring matching is enabled. |
| * |
| * @return <code>true</code> if substring matching is enabled |
| */ |
| protected boolean isSubstringMatching() { |
| String value = DLTKCore.getOption(DLTKCore.CODEASSIST_SUBSTRING_MATCH); |
| return DLTKCore.ENABLED.equals(value); |
| } |
| |
| @Override |
| public String getAdditionalProposalInfo() { |
| final Object info = getAdditionalProposalInfo( |
| new NullProgressMonitor()); |
| return info != null ? info.toString() : null; |
| } |
| |
| @Override |
| public Object getAdditionalProposalInfo(IProgressMonitor monitor) { |
| if (getProposalInfo() != null) { |
| String info = getProposalInfo().getInfo(monitor); |
| if (info != null && info.length() > 0) { |
| StringBuffer buffer = new StringBuffer(); |
| HTMLPrinter.insertPageProlog(buffer, 0, getCSSStyles()); |
| buffer.append(info); |
| HTMLPrinter.addPageEpilog(buffer); |
| info = buffer.toString(); |
| } |
| return info; |
| } |
| return null; |
| } |
| |
| /** |
| * Returns the style sheet URL. |
| * |
| * |
| */ |
| protected String getCSSStyles() { |
| if (fgCSSStyles == null) { |
| Bundle bundle = Platform.getBundle(DLTKUIPlugin.getPluginId()); |
| URL url = bundle.getEntry("/DocumentationHoverStyleSheet.css"); //$NON-NLS-1$ |
| if (url != null) { |
| try { |
| url = FileLocator.toFileURL(url); |
| BufferedReader reader = new BufferedReader( |
| new InputStreamReader(url.openStream())); |
| StringBuffer buffer = new StringBuffer(200); |
| String line = reader.readLine(); |
| while (line != null) { |
| buffer.append(line); |
| buffer.append('\n'); |
| line = reader.readLine(); |
| } |
| fgCSSStyles = buffer.toString(); |
| } catch (IOException ex) { |
| DLTKUIPlugin.log(ex); |
| } |
| } |
| } |
| String css = fgCSSStyles; |
| if (css != null) { |
| FontData fontData = JFaceResources.getFontRegistry().getFontData( |
| PreferenceConstants.APPEARANCE_DOCUMENTATION_FONT)[0]; |
| css = HTMLPrinter.convertTopLevelFont(css, fontData); |
| } |
| return css; |
| } |
| |
| @Override |
| public int getContextInformationPosition() { |
| if (getContextInformation() == null) |
| return getReplacementOffset() - 1; |
| return getReplacementOffset() + getCursorPosition(); |
| } |
| |
| /** |
| * Gets the replacement offset. |
| * |
| * @return Returns a int |
| */ |
| public int getReplacementOffset() { |
| return fReplacementOffset; |
| } |
| |
| /** |
| * Sets the replacement offset. |
| * |
| * @param replacementOffset |
| * The replacement offset to set |
| */ |
| public void setReplacementOffset(int replacementOffset) { |
| Assert.isTrue(replacementOffset >= 0); |
| fReplacementOffset = replacementOffset; |
| } |
| |
| @Override |
| public int getPrefixCompletionStart(IDocument document, |
| int completionOffset) { |
| return getReplacementOffset(); |
| } |
| |
| /** |
| * Gets the replacement length. |
| * |
| * @return Returns a int |
| */ |
| public int getReplacementLength() { |
| return fReplacementLength; |
| } |
| |
| /** |
| * Sets the replacement length. |
| * |
| * @param replacementLength |
| * The replacementLength to set |
| */ |
| public void setReplacementLength(int replacementLength) { |
| Assert.isTrue(replacementLength >= 0); |
| fReplacementLength = replacementLength; |
| } |
| |
| /** |
| * Gets the replacement string. |
| * |
| * @return Returns a String |
| */ |
| public String getReplacementString() { |
| return fReplacementString; |
| } |
| |
| /** |
| * Sets the replacement string. |
| * |
| * @param replacementString |
| * The replacement string to set |
| */ |
| public void setReplacementString(String replacementString) { |
| Assert.isNotNull(replacementString); |
| fReplacementString = replacementString; |
| } |
| |
| @Override |
| public CharSequence getPrefixCompletionText(IDocument document, |
| int completionOffset) { |
| if (!isCamelCaseMatching()) |
| return getReplacementString(); |
| |
| String prefix = getPrefix(document, completionOffset); |
| return getCamelCaseCompound(prefix, getReplacementString()); |
| } |
| |
| @Override |
| public Image getImage() { |
| return fImage; |
| } |
| |
| /** |
| * Sets the image. |
| * |
| * @param image |
| * The image to set |
| */ |
| public void setImage(Image image) { |
| fImage = image; |
| } |
| |
| @Override |
| public boolean isValidFor(IDocument document, int offset) { |
| return validate(document, offset, null); |
| } |
| |
| @Override |
| public boolean validate(IDocument document, int offset, |
| DocumentEvent event) { |
| |
| if (offset < getReplacementOffset()) |
| return false; |
| |
| boolean validated = isValidPrefix(getPrefix(document, offset)); |
| |
| if (validated && event != null) { |
| // adapt replacement range to document change |
| int delta = (event.fText == null ? 0 : event.fText.length()) |
| - event.fLength; |
| final int newLength = Math.max(getReplacementLength() + delta, 0); |
| setReplacementLength(newLength); |
| } |
| |
| return validated; |
| } |
| |
| /** |
| * Checks whether <code>prefix</code> is a valid prefix for this proposal. |
| * Usually, while code completion is in progress, the user types and edits |
| * the prefix in the document in order to filter the proposal list. From |
| * {@link #validate(IDocument, int, DocumentEvent) }, the current prefix in |
| * the document is extracted and this method is called to find out whether |
| * the proposal is still valid. |
| * <p> |
| * The default implementation checks if <code>prefix</code> is a prefix of |
| * the proposal's {@link #getDisplayString() display string} using the |
| * {@link #isPrefix(String, String) } method. |
| * </p> |
| * |
| * @param prefix |
| * the current prefix in the document |
| * @return <code>true</code> if <code>prefix</code> is a valid prefix of |
| * this proposal |
| */ |
| protected boolean isValidPrefix(String prefix) { |
| /* |
| * See http://dev.eclipse.org/bugs/show_bug.cgi?id=17667 why we do not |
| * use the replacement string. String word= fReplacementString; |
| */ |
| return isPrefix(prefix, TextProcessor.deprocess(getDisplayString())); |
| } |
| |
| /** |
| * Gets the proposal's relevance. |
| * |
| * @return Returns a int |
| */ |
| @Override |
| public int getRelevance() { |
| if (fPatternMatchRule == SearchPattern.R_SUBSTRING_MATCH) { |
| return fRelevance - 400; |
| } |
| return fRelevance; |
| } |
| |
| /** |
| * Sets the proposal's relevance. |
| * |
| * @param relevance |
| * The relevance to set |
| */ |
| public void setRelevance(int relevance) { |
| fRelevance = relevance; |
| } |
| |
| /** |
| * Returns the text in <code>document</code> from |
| * {@link #getReplacementOffset()} to <code>offset</code>. Returns the empty |
| * string if <code>offset</code> is before the replacement offset or if an |
| * exception occurs when accessing the document. |
| * |
| * |
| */ |
| protected String getPrefix(IDocument document, int offset) { |
| try { |
| int length = offset - getReplacementOffset(); |
| if (length > 0) |
| return document.get(getReplacementOffset(), length); |
| } catch (BadLocationException x) { |
| } |
| return ""; //$NON-NLS-1$ |
| } |
| |
| /** |
| * Case insensitive comparison of <code>prefix</code> with the start of |
| * <code>string</code>. Returns <code>false</code> if <code>prefix</code> is |
| * longer than <code>string</code> |
| * |
| * |
| */ |
| protected boolean isPrefix(String prefix, String string) { |
| if (prefix == null || string == null |
| || prefix.length() > string.length()) |
| return false; |
| fPatternMatchRule = getPatternMatchRule(prefix, string); |
| return fPatternMatchRule != -1; |
| } |
| |
| /** |
| * Matches <code>prefix</code> against <code>string</code> and replaces the |
| * matched region by prefix. Case is preserved as much as possible. This |
| * method returns <code>string</code> if camel case completion is disabled. |
| * Examples when camel case completion is enabled: |
| * <ul> |
| * <li>getCamelCompound("NuPo", "NullPointerException") -> |
| * "NuPointerException"</li> |
| * <li>getCamelCompound("NuPoE", "NullPointerException") -> |
| * "NuPoException"</li> |
| * <li>getCamelCompound("hasCod", "hashCode") -> "hasCode"</li> |
| * </ul> |
| * |
| * @param prefix |
| * the prefix to match against |
| * @param string |
| * the string to match |
| * @return a compound of prefix and any postfix taken from |
| * <code>string</code> |
| * |
| */ |
| protected final String getCamelCaseCompound(String prefix, String string) { |
| if (prefix.length() > string.length()) |
| return string; |
| |
| // a normal prefix - no camel case logic at all |
| String start = string.substring(0, prefix.length()); |
| if (start.equalsIgnoreCase(prefix)) |
| return string; |
| |
| final char[] patternChars = prefix.toCharArray(); |
| final char[] stringChars = string.toCharArray(); |
| |
| for (int i = 1; i <= stringChars.length; i++) |
| if (CharOperation.camelCaseMatch(patternChars, 0, |
| patternChars.length, stringChars, 0, i)) |
| return prefix + string.substring(i); |
| |
| // Not a camel case match at all. |
| // This should not happen -> stay with the default behavior |
| return string; |
| } |
| |
| /** |
| * Returns true if camel case matching is enabled. |
| */ |
| protected boolean isCamelCaseMatching() { |
| String value = DLTKCore.getOption(DLTKCore.CODEASSIST_CAMEL_CASE_MATCH); |
| return DLTKCore.ENABLED.equals(value); |
| } |
| |
| protected IPreferenceStore getPreferenceStore() { |
| return DLTKUIPlugin.getDefault().getPreferenceStore(); |
| } |
| |
| protected boolean insertCompletion() { |
| return getPreferenceStore() |
| .getBoolean(PreferenceConstants.CODEASSIST_INSERT_COMPLETION); |
| } |
| |
| protected Color getForegroundColor(StyledText text) { |
| |
| RGB rgb = PreferenceConverter.getColor(getPreferenceStore(), |
| PreferenceConstants.CODEASSIST_REPLACEMENT_FOREGROUND); |
| ScriptTextTools textTools = getTextTools(); |
| if (textTools == null) { |
| return null; |
| } |
| return textTools.getColorManager().getColor(rgb); |
| } |
| |
| protected ScriptTextTools getTextTools() { |
| return null; |
| } |
| |
| protected Color getBackgroundColor(StyledText text) { |
| |
| RGB rgb = PreferenceConverter.getColor(getPreferenceStore(), |
| PreferenceConstants.CODEASSIST_REPLACEMENT_BACKGROUND); |
| ScriptTextTools textTools = getTextTools(); |
| if (textTools != null) |
| return textTools.getColorManager().getColor(rgb); |
| return null; |
| } |
| |
| private void repairPresentation(ITextViewer viewer) { |
| if (fRememberedStyleRange != null) { |
| if (viewer instanceof ITextViewerExtension2) { |
| // attempts to reduce the redraw area |
| ITextViewerExtension2 viewer2 = (ITextViewerExtension2) viewer; |
| |
| if (viewer instanceof ITextViewerExtension5) { |
| |
| ITextViewerExtension5 extension = (ITextViewerExtension5) viewer; |
| IRegion modelRange = extension.widgetRange2ModelRange( |
| new Region(fRememberedStyleRange.start, |
| fRememberedStyleRange.length)); |
| if (modelRange != null) |
| viewer2.invalidateTextPresentation( |
| modelRange.getOffset(), modelRange.getLength()); |
| |
| } else { |
| viewer2.invalidateTextPresentation( |
| fRememberedStyleRange.start |
| + viewer.getVisibleRegion().getOffset(), |
| fRememberedStyleRange.length); |
| } |
| |
| } else |
| viewer.invalidateTextPresentation(); |
| } |
| } |
| |
| private void updateStyle(ITextViewer viewer) { |
| |
| StyledText text = viewer.getTextWidget(); |
| if (text == null || text.isDisposed()) |
| return; |
| |
| int widgetCaret = text.getCaretOffset(); |
| |
| int modelCaret = 0; |
| if (viewer instanceof ITextViewerExtension5) { |
| ITextViewerExtension5 extension = (ITextViewerExtension5) viewer; |
| modelCaret = extension.widgetOffset2ModelOffset(widgetCaret); |
| } else { |
| IRegion visibleRegion = viewer.getVisibleRegion(); |
| modelCaret = widgetCaret + visibleRegion.getOffset(); |
| } |
| |
| if (modelCaret >= getReplacementOffset() + getReplacementLength()) { |
| repairPresentation(viewer); |
| return; |
| } |
| |
| int offset = widgetCaret; |
| int length = getReplacementOffset() + getReplacementLength() |
| - modelCaret; |
| |
| Color foreground = getForegroundColor(text); |
| Color background = getBackgroundColor(text); |
| |
| StyleRange range = text.getStyleRangeAtOffset(offset); |
| int fontStyle = range != null ? range.fontStyle : SWT.NORMAL; |
| |
| repairPresentation(viewer); |
| fRememberedStyleRange = new StyleRange(offset, length, foreground, |
| background, fontStyle); |
| if (range != null) { |
| fRememberedStyleRange.strikeout = range.strikeout; |
| fRememberedStyleRange.underline = range.underline; |
| } |
| |
| // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754 |
| try { |
| text.setStyleRange(fRememberedStyleRange); |
| } catch (IllegalArgumentException x) { |
| // catching exception as offset + length might be outside of the |
| // text widget |
| fRememberedStyleRange = null; |
| } |
| } |
| |
| @Override |
| public void selected(ITextViewer viewer, boolean smartToggle) { |
| if (!insertCompletion() ^ smartToggle) |
| updateStyle(viewer); |
| else { |
| repairPresentation(viewer); |
| fRememberedStyleRange = null; |
| } |
| } |
| |
| @Override |
| public void unselected(ITextViewer viewer) { |
| repairPresentation(viewer); |
| fRememberedStyleRange = null; |
| } |
| |
| @Override |
| public IInformationControlCreator getInformationControlCreator() { |
| Shell shell = DLTKUIPlugin.getActiveWorkbenchShell(); |
| if (shell == null || !BrowserInformationControl.isAvailable(shell)) |
| return null; |
| if (fCreator == null) { |
| DocumentationHover.PresenterControlCreator presenterControlCreator = new DocumentationHover.PresenterControlCreator( |
| getSite()); |
| fCreator = new DocumentationHover.HoverControlCreator( |
| presenterControlCreator, true); |
| } |
| return fCreator; |
| } |
| |
| private IWorkbenchSite getSite() { |
| final IWorkbenchPage page = DLTKUIPlugin.getActivePage(); |
| if (page != null) { |
| final IWorkbenchPart part = page.getActivePart(); |
| if (part != null) |
| return part.getSite(); |
| } |
| return null; |
| } |
| |
| public String getSortString() { |
| return fSortString; |
| } |
| |
| public void setSortString(String string) { |
| fSortString = string; |
| } |
| |
| protected ITextViewer getTextViewer() { |
| return fTextViewer; |
| } |
| |
| protected boolean isToggleEating() { |
| return fToggleEating; |
| } |
| |
| /** |
| * Sets up a simple linked mode at {@link #getCursorPosition()} and an exit |
| * policy that will exit the mode when <code>closingCharacter</code> is |
| * typed and an exit position at <code>getCursorPosition() + 1</code>. |
| * |
| * @param document |
| * the document |
| * @param closingCharacter |
| * the exit character |
| */ |
| protected void setUpLinkedMode(IDocument document, char closingCharacter) { |
| if (getTextViewer() != null && autocloseBrackets()) { |
| int offset = getReplacementOffset() + getCursorPosition(); |
| int exit = getReplacementOffset() + getReplacementString().length(); |
| try { |
| LinkedPositionGroup group = new LinkedPositionGroup(); |
| group.addPosition(new LinkedPosition(document, offset, 0, |
| LinkedPositionGroup.NO_STOP)); |
| |
| LinkedModeModel model = new LinkedModeModel(); |
| model.addGroup(group); |
| model.forceInstall(); |
| |
| LinkedModeUI ui = new EditorLinkedModeUI(model, |
| getTextViewer()); |
| ui.setSimpleMode(true); |
| ui.setExitPolicy(new ExitPolicy(closingCharacter, document)); |
| ui.setExitPosition(getTextViewer(), exit, 0, Integer.MAX_VALUE); |
| ui.setCyclingMode(LinkedModeUI.CYCLE_NEVER); |
| ui.enter(); |
| } catch (BadLocationException x) { |
| DLTKUIPlugin.log(x); |
| } |
| } |
| } |
| |
| protected boolean autocloseBrackets() { |
| return getPreferenceStore() |
| .getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACKETS); |
| } |
| |
| protected void setDisplayString(String string) { |
| fDisplayString = new StyledString(string); |
| } |
| |
| @Override |
| public String toString() { |
| return getDisplayString(); |
| } |
| |
| /** |
| * Returns the script element proposed by the receiver, possibly |
| * <code>null</code>. |
| * |
| * @return the script element proposed by the receiver, possibly |
| * <code>null</code> |
| */ |
| public IModelElement getModelElement() { |
| final ICompletionProposalInfo proposalInfo = getProposalInfo(); |
| if (proposalInfo != null && proposalInfo instanceof ProposalInfo) { |
| try { |
| return ((ProposalInfo) proposalInfo).getModelElement(); |
| } catch (ModelException x) { |
| DLTKUIPlugin.log(x); |
| } |
| } |
| return null; |
| } |
| |
| /** |
| * Returns a deep copy of the given styles string. |
| */ |
| public static StyledString copyStyledString( |
| final StyledString displayString) { |
| final StyledString copy = new StyledString(displayString.getString()); |
| for (final StyleRange range : displayString.getStyleRanges()) { |
| copy.setStyle(range.start, range.length, new Styler() { |
| |
| @Override |
| public void applyStyles(final TextStyle textStyle) { |
| textStyle.background = range.background; |
| textStyle.borderColor = range.borderColor; |
| textStyle.borderStyle = range.borderStyle; |
| textStyle.font = range.font; |
| textStyle.foreground = range.foreground; |
| } |
| }); |
| } |
| return copy; |
| } |
| } |