blob: 93e0ec6097b76e1a567f36b2024a5cf64a3b93e6 [file] [log] [blame]
/*******************************************************************************
* 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;
}
}