/* * Copyright (c) 2012, the Dart project authors. * * Licensed under the Eclipse Public License v1.0 (the "License"); you may not use this file except * in compliance with the License. You may obtain a copy of the License at * * http://www.eclipse.org/legal/epl-v10.html * * Unless required by applicable law or agreed to in writing, software distributed under the License * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express * or implied. See the License for the specific language governing permissions and limitations under * the License. */ package com.google.dart.tools.ui.internal.text.completion; import com.google.dart.engine.utilities.general.CharOperation; import com.google.dart.engine.utilities.instrumentation.Instrumentation; import com.google.dart.engine.utilities.instrumentation.InstrumentationBuilder; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.completion.CompletionProposal; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.PreferenceConstants; import com.google.dart.tools.ui.internal.text.dart.SmartSemicolonAutoEditStrategy; import com.google.dart.tools.ui.internal.text.html.HTMLPrinter; import com.google.dart.tools.ui.text.DartPartitions; import com.google.dart.tools.ui.text.DartTextTools; import com.google.dart.tools.ui.text.dart.DartContentAssistInvocationContext; import com.google.dart.tools.ui.text.dart.IDartCompletionProposal; import org.eclipse.core.runtime.Assert; import org.eclipse.core.runtime.FileLocator; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.core.runtime.NullProgressMonitor; import org.eclipse.core.runtime.Platform; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.resource.JFaceResources; import org.eclipse.jface.text.AbstractReusableInformationControlCreator; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.BadPositionCategoryException; import org.eclipse.jface.text.DefaultPositionUpdater; import org.eclipse.jface.text.DocumentCommand; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IInformationControl; import org.eclipse.jface.text.IInformationControlCreator; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextPresentationListener; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.ITextViewerExtension2; import org.eclipse.jface.text.ITextViewerExtension4; import org.eclipse.jface.text.ITextViewerExtension5; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextPresentation; 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.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.text.source.SourceViewer; import org.eclipse.jface.viewers.StyledString; 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.widgets.Display; 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; import java.io.BufferedReader; import java.io.IOException; import java.io.InputStreamReader; import java.net.URL; public abstract class AbstractDartCompletionProposal implements IDartCompletionProposal, ICompletionProposalExtension, ICompletionProposalExtension2, ICompletionProposalExtension3, ICompletionProposalExtension5, ICompletionProposalExtension6 { /** * Presenter control creator. */ public static final class PresenterControlCreator extends AbstractReusableInformationControlCreator { @Override @SuppressWarnings("restriction") public IInformationControl doCreateInformationControl(Shell parent) { String font = PreferenceConstants.APPEARANCE_JAVADOC_FONT; return new org.eclipse.jface.internal.text.html.BrowserInformationControl(parent, font, true); } } /** * Allow the linked mode editor to continue running even when the exit character is typed as part * of a function argument. Using shift operators in a context that expects balanced angle brackets * is not legal syntax and will confuse the linked mode editor. */ protected class ExitPolicy implements IExitPolicy { private int parenCount = 0; private int braceCount = 0; private int bracketCount = 0; private int angleBracketCount = 0; private char lastChar = (char) 0; 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) { countGroupChars(event); if (event.character == fExitCharacter && isBalanced(fExitCharacter)) { if (environment.anyPositionContains(offset)) { return new ExitFlags(ILinkedModeListener.UPDATE_CARET, false); } else { return new ExitFlags(ILinkedModeListener.UPDATE_CARET, true); } } switch (event.character) { case ';': return new ExitFlags(ILinkedModeListener.EXTERNAL_MODIFICATION | ILinkedModeListener.UPDATE_CARET | ILinkedModeListener.EXIT_ALL, true); case '\b': if (fInvocationContext != null) { if (fInvocationContext.getViewer().getSelectedRange().y > 0) { return new ExitFlags(ILinkedModeListener.EXTERNAL_MODIFICATION, true); } } return null; case SWT.CR: // when entering a function 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) { } } return null; // case ',': // // Making comma act like tab seems like a good idea but it requires auto-insert of matching group chars to work. // if (offset > 0) { // try { // if (fDocument.getChar(offset) == ',') { // event.character = 0x09; // return null; // } // } catch (BadLocationException e) { // } // } default: return null; } } private void countGroupChar(char ch, int inc) { switch (ch) { case '(': parenCount += inc; break; case ')': parenCount -= inc; break; case '{': braceCount += inc; break; case '}': braceCount -= inc; break; case '[': bracketCount += inc; break; case ']': bracketCount -= inc; break; case '<': angleBracketCount += inc; break; case '>': if (lastChar != '=') { // only decrement when not part of => angleBracketCount -= inc; } break; case '=': if (lastChar == '>') { // deleting => should not change angleBracketCount angleBracketCount += inc; } break; default: break; } lastChar = ch; } private void countGroupChars(VerifyEvent event) { char ch = event.character; int inc = 1; if (ch == '\b') { // TODO Find correct delete chars for Linux & Windows inc = -1; if (!(event.widget instanceof StyledText)) { return; } Point sel = ((StyledText) event.widget).getSelection(); try { if (sel.x == sel.y) { ch = fDocument.getChar(sel.x); countGroupChar(ch, inc); } else { for (int x = sel.y - 1; x >= sel.x; x--) { ch = fDocument.getChar(x); countGroupChar(ch, inc); } } } catch (BadLocationException ex) { return; } } else { countGroupChar(ch, inc); } } private boolean isBalanced(char ch) { switch (ch) { case ')': return parenCount == -1; case '}': return braceCount == -1; case ']': return bracketCount == -1; case '>': return angleBracketCount == -1; default: return true; // never unbalanced } } } /** * 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 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. * @return offset after the replace */ public int postReplace(IDocument document) { try { document.removePosition(CATEGORY, fPosition); document.removePositionUpdater(fPositionUpdater); document.removePositionCategory(CATEGORY); } catch (BadPositionCategoryException e) { // should not happen DartToolsPlugin.log(e); } return fPosition.getOffset(); } /** * 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. * @param offset the offset * @throws BadLocationException if the offset describes an invalid range in this document */ 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 DartToolsPlugin.log(e); } } } /** * The control creator. */ private static final class ControlCreator extends AbstractReusableInformationControlCreator { @Override @SuppressWarnings("restriction") public IInformationControl doCreateInformationControl(Shell parent) { String font = PreferenceConstants.APPEARANCE_JAVADOC_FONT; return new org.eclipse.jface.internal.text.html.BrowserInformationControl( parent, font, DartToolsPlugin.getAdditionalInfoAffordanceString()) { @Override public IInformationControlCreator getInformationPresenterControlCreator() { return new PresenterControlCreator(); } }; } } protected static boolean insertCompletion() { IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore(); return preference.getBoolean(PreferenceConstants.CODEASSIST_INSERT_COMPLETION); } private static Color getBackgroundColor() { IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore(); RGB rgb = PreferenceConverter.getColor( preference, PreferenceConstants.CODEASSIST_REPLACEMENT_BACKGROUND); DartTextTools textTools = DartToolsPlugin.getDefault().getDartTextTools(); return textTools.getColorManager().getColor(rgb); } private static Color getForegroundColor() { IPreferenceStore preference = DartToolsPlugin.getDefault().getPreferenceStore(); RGB rgb = PreferenceConverter.getColor( preference, PreferenceConstants.CODEASSIST_REPLACEMENT_FOREGROUND); DartTextTools textTools = DartToolsPlugin.getDefault().getDartTextTools(); return textTools.getColorManager().getColor(rgb); } private StyledString fDisplayString; private String fReplacementString; private int fReplacementOffset; private int fReplacementLength; private int fReplacementLengthIdentifier; private int fCursorPosition; private Image fImage; private IContextInformation fContextInformation; private ProposalInfo fProposalInfo; private char[] fTriggerCharacters; private String fSortString; private int fRelevance; private boolean fIsInJavadoc; private StyleRange fRememberedStyleRange; private boolean fToggleEating; private ITextViewer fTextViewer; /** * The control creator. */ private IInformationControlCreator fCreator; /** * The CSS used to format javadoc information. */ private static String fgCSSStyles; /** * The invocation context of this completion proposal. Can be <code>null</code>. */ protected final DartContentAssistInvocationContext fInvocationContext; /** * Cache to store last validation state. */ private boolean fIsValidated = true; /** * The text presentation listener. */ private ITextPresentationListener fTextPresentationListener; protected boolean triggerCompletionAfterApply = false; protected AbstractDartCompletionProposal() { fInvocationContext = null; } protected AbstractDartCompletionProposal(DartContentAssistInvocationContext context) { fInvocationContext = context; } @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) { InstrumentationBuilder instrumentation = Instrumentation.builder("CompletionProposal-Apply"); instrumentation.metric("Trigger", trigger); try { // if (isSupportingRequiredProposals()) { // CompletionProposal coreProposal = ((MemberProposalInfo) getProposalInfo()).fProposal; // CompletionProposal[] requiredProposals = coreProposal.getRequiredProposals(); // for (int i = 0; requiredProposals != null && i < requiredProposals.length; i++) { // int oldLen = document.getLength(); // if (requiredProposals[i].getKind() == CompletionProposal.TYPE_REF) { // LazyDartCompletionProposal proposal = createRequiredTypeCompletionProposal( // requiredProposals[i], // fInvocationContext); // proposal.apply(document); // setReplacementOffset(getReplacementOffset() + document.getLength() - oldLen); // } else { // /* // * we only support the above required proposals, see // * CompletionProposal#getRequiredProposals() // */ // Assert.isTrue(false); // } // } // } try { boolean isSmartTrigger = isSmartTrigger(trigger); instrumentation.metric("isSmartTrigger", isSmartTrigger); String replacement; if (isSmartTrigger || trigger == (char) 0) { replacement = getReplacementString(); } else { StringBuffer buffer = new StringBuffer(getReplacementString()); // fix for PR #5533. Assumes that no eating takes place. if ((getCursorPosition() > 0 && getCursorPosition() <= buffer.length() && buffer.charAt(getCursorPosition() - 1) != trigger)) { buffer.insert(getCursorPosition(), trigger); setCursorPosition(getCursorPosition() + 1); } replacement = buffer.toString(); setReplacementString(replacement); } instrumentation.data("Replacement", replacement); // 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); referenceOffset = referenceTracker.postReplace(document); int delta = replacement == null ? 0 : replacement.length(); if (delta > 0 && replacement.charAt(replacement.length() - 1) == ']') { delta += 1; } setReplacementOffset(referenceOffset - delta); // PR 47097 if (isSmartTrigger) { handleSmartTrigger(document, trigger, referenceOffset); } } catch (BadLocationException x) { instrumentation.metric("Problem", "BadLocationException"); // ignore } } finally { instrumentation.log(); } } @Override public void apply(final ITextViewer viewer, char trigger, int stateMask, int offset) { IDocument document = viewer.getDocument(); if (fTextViewer == null) { fTextViewer = viewer; } // don't apply the proposal if for some reason we're not valid any longer if (!isInDartDoc() && !validate(document, offset, null)) { setCursorPosition(offset); if (trigger != '\0') { try { document.replace(offset, 0, String.valueOf(trigger)); setCursorPosition(getCursorPosition() + 1); if (trigger == '(' && autocloseBrackets()) { document.replace(getReplacementOffset() + getCursorPosition(), 0, ")"); //$NON-NLS-1$ setUpLinkedMode(document, ')'); } } catch (BadLocationException x) { // ignore } } return; } // don't eat if not in preferences, XOR with Ctrl // but: if there is a selection, replace it! Point selection = viewer.getSelectedRange(); int newLength = selection.x + selection.y - getReplacementOffset(); fToggleEating = (stateMask & SWT.CTRL) != 0; if (fToggleEating) { newLength = getReplacementLengthIdentifier(); } if (newLength >= 0) { setReplacementLength(newLength); } apply(document, trigger, offset); fToggleEating = false; if (triggerCompletionAfterApply) { if (viewer instanceof SourceViewer) { // run asynchronously to allow cursor to move Display.getDefault().asyncExec(new Runnable() { @Override public void run() { ((SourceViewer) viewer).doOperation(SourceViewer.CONTENTASSIST_PROPOSALS); } }); } } } @Override public String getAdditionalProposalInfo() { Object info = getAdditionalProposalInfo(new NullProgressMonitor()); return info == null ? null : info.toString(); } @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; } @Override public IContextInformation getContextInformation() { return fContextInformation; } @Override public int getContextInformationPosition() { if (getContextInformation() == null) { return getReplacementOffset() - 1; } return getReplacementOffset() + getCursorPosition(); } @Override public String getDisplayString() { if (fDisplayString != null) { return fDisplayString.getString(); } return ""; //$NON-NLS-1$ } @Override public Image getImage() { return fImage; } @Override @SuppressWarnings("restriction") public IInformationControlCreator getInformationControlCreator() { // TODO(scheglov) Linux is known to crash sometimes when we create Browser. // https://code.google.com/p/dart/issues/detail?id=12903 // It always was like this. if (DartCore.isLinux()) { return null; } // For luckier OSes. Shell shell = DartToolsPlugin.getActiveWorkbenchShell(); if (shell == null || !org.eclipse.jface.internal.text.html.BrowserInformationControl.isAvailable(shell)) { return null; } if (fCreator == null) { fCreator = new ControlCreator(); } return fCreator; } @Override public int getPrefixCompletionStart(IDocument document, int completionOffset) { return getReplacementOffset(); } @Override public CharSequence getPrefixCompletionText(IDocument document, int completionOffset) { if (!isCamelCaseMatching()) { return getReplacementString(); } String prefix = getPrefix(document, completionOffset); return getCamelCaseCompound(prefix, getReplacementString()); } /** * Gets the proposal's relevance. * * @return Returns an int */ @Override public int getRelevance() { return fRelevance; } /** * Gets the replacement length. * * @return Returns an int */ public int getReplacementLength() { return fReplacementLength; } /** * Gets the replacement length for identifier. * * @return Returns an int */ public int getReplacementLengthIdentifier() { return fReplacementLengthIdentifier; } /** * Gets the replacement offset. * * @return Returns an int */ public int getReplacementOffset() { return fReplacementOffset; } /** * Gets the replacement string. * * @return Returns a String */ public String getReplacementString() { return fReplacementString; } @Override public Point getSelection(IDocument document) { if (!fIsValidated) { return null; } return new Point(getReplacementOffset() + getCursorPosition(), 0); } public String getSortString() { return fSortString; } @Override public StyledString getStyledDisplayString() { return fDisplayString; } @Override public char[] getTriggerCharacters() { return fTriggerCharacters; } @Override public boolean isValidFor(IDocument document, int offset) { return validate(document, offset, null); } @Override public void selected(final ITextViewer viewer, boolean smartToggle) { repairPresentation(viewer); fRememberedStyleRange = null; if (!insertCompletion() ^ smartToggle) { StyleRange range = createStyleRange(viewer); if (range == null) { return; } fRememberedStyleRange = range; if (viewer instanceof ITextViewerExtension4) { if (fTextPresentationListener == null) { fTextPresentationListener = new ITextPresentationListener() { @Override public void applyTextPresentation(TextPresentation textPresentation) { fRememberedStyleRange = createStyleRange(viewer); if (fRememberedStyleRange != null) { textPresentation.mergeStyleRange(fRememberedStyleRange); } } }; ((ITextViewerExtension4) viewer).addTextPresentationListener(fTextPresentationListener); } repairPresentation(viewer); } else { updateStyle(viewer); } } } /** * Sets the context information. * * @param contextInformation The context information associated with this proposal */ public void setContextInformation(IContextInformation contextInformation) { fContextInformation = contextInformation; } /** * 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; } /** * Sets the image. * * @param image The image to set */ public void setImage(Image image) { fImage = image; } /** * Sets the proposal info. * * @param proposalInfo The additional information associated with this proposal or * <code>null</code> */ public void setProposalInfo(ProposalInfo proposalInfo) { fProposalInfo = proposalInfo; } /** * Sets the proposal's relevance. * * @param relevance The relevance to set */ public void setRelevance(int relevance) { fRelevance = relevance; } /** * Sets the replacement length. * * @param replacementLength The replacementLength to set */ public void setReplacementLength(int replacementLength) { Assert.isTrue(replacementLength >= 0); fReplacementLength = replacementLength; } /** * Sets the replacement length for identifier. * * @param length The replacementLength to set */ public void setReplacementLengthIdentifier(int length) { Assert.isTrue(length >= 0); fReplacementLengthIdentifier = length; } /** * Sets the replacement offset. * * @param replacementOffset The replacement offset to set */ public void setReplacementOffset(int replacementOffset) { Assert.isTrue(replacementOffset >= 0); fReplacementOffset = replacementOffset; } /** * Sets the replacement string. * * @param replacementString The replacement string to set */ public void setReplacementString(String replacementString) { Assert.isNotNull(replacementString); fReplacementString = replacementString; } public void setStyledDisplayString(StyledString text) { fDisplayString = text; } /** * 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; } @Override public String toString() { return getDisplayString(); } @Override public void unselected(ITextViewer viewer) { if (fTextPresentationListener != null) { ((ITextViewerExtension4) viewer).removeTextPresentationListener(fTextPresentationListener); fTextPresentationListener = null; } repairPresentation(viewer); fRememberedStyleRange = null; } @Override public boolean validate(IDocument document, int offset, DocumentEvent event) { if (!isOffsetValid(offset)) { return fIsValidated = false; } fIsValidated = isValidPrefix(getPrefix(document, offset)); if (fIsValidated && 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 fIsValidated; } protected boolean autocloseBrackets() { IPreferenceStore preferenceStore = DartToolsPlugin.getDefault().getPreferenceStore(); return preferenceStore.getBoolean(PreferenceConstants.EDITOR_CLOSE_BRACKETS); } /** * 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 the style information for displaying HTML content. * * @return the CSS styles */ protected String getCSSStyles() { if (fgCSSStyles == null) { Bundle bundle = Platform.getBundle(DartToolsPlugin.getPluginId()); URL url = bundle.getEntry("/DartdocHoverStyleSheet.css"); //$NON-NLS-1$ if (url != null) { BufferedReader reader = null; try { url = FileLocator.toFileURL(url); 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) { DartToolsPlugin.log(ex); } finally { try { if (reader != null) { reader.close(); } } catch (IOException e) { } } } } String css = fgCSSStyles; if (css != null) { FontData fontData = JFaceResources.getFontRegistry().getFontData( PreferenceConstants.APPEARANCE_JAVADOC_FONT)[0]; css = HTMLPrinter.convertTopLevelFont(css, fontData); } return css; } protected int getCursorPosition() { return fCursorPosition; } /** * 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. * * @param document the document * @param offset the offset * @return the prefix */ 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$ } /** * Returns the additional proposal info, or <code>null</code> if none exists. * * @return the additional proposal info, or <code>null</code> if none exists */ protected ProposalInfo getProposalInfo() { return fProposalInfo; } protected ITextViewer getTextViewer() { return fTextViewer; } /** * Returns true if camel case matching is enabled. * * @return <code>true</code> if camel case matching is enabled */ protected boolean isCamelCaseMatching() { return true; } /** * Returns <code>true</code> if the proposal is within Dart doc, <code>false</code> otherwise. * * @return <code>true</code> if the proposal is within Dart doc, <code>false</code> otherwise */ protected boolean isInDartDoc() { return fIsInJavadoc; } /** * Tells whether the user toggled the insert mode by pressing the 'Ctrl' key. * * @return <code>true</code> if the insert mode is toggled, <code>false</code> otherwise */ protected boolean isInsertModeToggled() { return fToggleEating; } /** * Checks whether the given offset is valid for this proposal. * * @param offset the caret offset * @return <code>true</code> if the offset is valid for this proposal */ protected boolean isOffsetValid(int offset) { return getReplacementOffset() <= offset; } /** * Case insensitive comparison of <code>prefix</code> with the start of <code>string</code>. * * @param prefix the prefix * @param string the string to look for the prefix * @return <code>true</code> if the string begins with the given prefix and <code>false</code> if * <code>prefix</code> is longer than <code>string</code> or the string doesn't start with * the given prefix */ protected boolean isPrefix(String prefix, String string) { if (prefix == null || string == null || prefix.length() > string.length()) { return false; } String start = string.substring(0, prefix.length()); return start.equalsIgnoreCase(prefix) || isCamelCaseMatching() && CharOperation.camelCaseMatch(prefix.toCharArray(), string.toCharArray()); } /** * Tells whether required proposals are supported by this proposal. * * @return <code>true</code> if required proposals are supported by this proposal */ protected boolean isSupportingRequiredProposals() { if (fInvocationContext == null) { return false; } ProposalInfo proposalInfo = getProposalInfo(); CompletionProposal proposal = proposalInfo.getProposal(); if (proposal == null) { return false; } int kind = proposal.getKind(); return (kind == CompletionProposal.METHOD_REF || kind == CompletionProposal.ARGUMENT_LIST || kind == CompletionProposal.FIELD_REF || kind == CompletionProposal.TYPE_REF // || kind == CompletionProposal.ANONYMOUS_CLASS_CONSTRUCTOR_INVOCATION || kind == CompletionProposal.CONSTRUCTOR_INVOCATION); } protected boolean isToggleEating() { return fToggleEating; } /** * 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; * * Besides that bug we also use the display string for performance reasons, as computing the * replacement string can be expensive. */ return isPrefix(prefix, TextProcessor.deprocess(getDisplayString())); } 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); } } protected void setDisplayString(String string) { fDisplayString = new StyledString(string); } /** * Sets the Dava doc attribute. * * @param isInJavadoc <code>true</code> if the proposal is within javadoc */ protected void setInDartDoc(boolean isInJavadoc) { fIsInJavadoc = isInJavadoc; } protected void setSortString(String string) { fSortString = string; } /** * 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) { DartToolsPlugin.log(x); } } } /** * Creates a style range for the text viewer. * * @param viewer the text viewer * @return the new style range for the text viewer or <code>null</code> */ private StyleRange createStyleRange(ITextViewer viewer) { StyledText text = viewer.getTextWidget(); if (text == null || text.isDisposed()) { return null; } 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()) { return null; } int length = getReplacementOffset() + getReplacementLength() - modelCaret; Color foreground = getForegroundColor(); Color background = getBackgroundColor(); return new StyleRange(modelCaret, length, foreground, background); } @SuppressWarnings("unused") private IWorkbenchSite getSite() { IWorkbenchPage page = DartToolsPlugin.getActivePage(); if (page != null) { IWorkbenchPart part = page.getActivePart(); if (part != null) { return part.getSite(); } } return null; } /** * Convert a document offset to the corresponding widget offset. * * @param viewer the text viewer * @param documentOffset the document offset * @return widget offset */ private int getWidgetOffset(ITextViewer viewer, int documentOffset) { if (viewer instanceof ITextViewerExtension5) { ITextViewerExtension5 extension = (ITextViewerExtension5) viewer; return extension.modelOffset2WidgetOffset(documentOffset); } IRegion visible = viewer.getVisibleRegion(); int widgetOffset = documentOffset - visible.getOffset(); if (widgetOffset > visible.getLength()) { return -1; } return widgetOffset; } private void handleSmartTrigger(IDocument document, char trigger, int referenceOffset) throws BadLocationException { DocumentCommand cmd = new DocumentCommand() { }; cmd.offset = referenceOffset; cmd.length = 0; cmd.text = Character.toString(trigger); cmd.doit = true; cmd.shiftsCaret = true; cmd.caretOffset = getReplacementOffset() + getCursorPosition(); SmartSemicolonAutoEditStrategy strategy = new SmartSemicolonAutoEditStrategy( DartPartitions.DART_PARTITIONING); strategy.customizeDocumentCommand(document, cmd); replace(document, cmd.offset, cmd.length, cmd.text); setCursorPosition(cmd.caretOffset - getReplacementOffset() + cmd.text.length()); } private boolean isSmartTrigger(char trigger) { return trigger == ';' && DartToolsPlugin.getDefault().getCombinedPreferenceStore().getBoolean( PreferenceConstants.EDITOR_SMART_SEMICOLON) || trigger == '{' && DartToolsPlugin.getDefault().getCombinedPreferenceStore().getBoolean( PreferenceConstants.EDITOR_SMART_OPENING_BRACE); } private void repairPresentation(ITextViewer viewer) { if (fRememberedStyleRange != null) { if (viewer instanceof ITextViewerExtension2) { // attempts to reduce the redraw area ITextViewerExtension2 viewer2 = (ITextViewerExtension2) viewer; viewer2.invalidateTextPresentation( fRememberedStyleRange.start, fRememberedStyleRange.length); } else { viewer.invalidateTextPresentation(); } } } private void updateStyle(ITextViewer viewer) { StyledText text = viewer.getTextWidget(); int widgetOffset = getWidgetOffset(viewer, fRememberedStyleRange.start); StyleRange range = new StyleRange(fRememberedStyleRange); range.start = widgetOffset; range.length = fRememberedStyleRange.length; StyleRange currentRange = text.getStyleRangeAtOffset(widgetOffset); if (currentRange != null) { range.strikeout = currentRange.strikeout; range.underline = currentRange.underline; range.fontStyle = currentRange.fontStyle; } // http://dev.eclipse.org/bugs/show_bug.cgi?id=34754 try { text.setStyleRange(range); } catch (IllegalArgumentException x) { // catching exception as offset + length might be outside of the text widget fRememberedStyleRange = null; } } }