/* * Copyright (c) 2014, 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.editor; import com.google.dart.server.generated.AnalysisServer; import com.google.dart.tools.core.DartCore; import com.google.dart.tools.core.DartCoreDebug; import com.google.dart.tools.core.analysis.model.AnalysisServerData; import com.google.dart.tools.core.analysis.model.AnalysisServerHighlightsListener; import com.google.dart.tools.ui.DartToolsPlugin; import com.google.dart.tools.ui.DartUI; import com.google.dart.tools.ui.internal.text.dart.DartReconcilingStrategy; import com.google.dart.tools.ui.text.IColorManager; import org.dartlang.analysis.server.protocol.HighlightRegion; import org.dartlang.analysis.server.protocol.HighlightRegionType; import org.eclipse.jface.preference.IPreferenceStore; import org.eclipse.jface.preference.PreferenceConverter; import org.eclipse.jface.text.Document; import org.eclipse.jface.text.DocumentEvent; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IPositionUpdater; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ISynchronizable; import org.eclipse.jface.text.ITextPresentationListener; import org.eclipse.jface.text.Position; import org.eclipse.jface.text.TextPresentation; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.graphics.RGB; import org.eclipse.swt.widgets.Display; /** * A helper for displaying {@link HighlightRegion} from {@link AnalysisServer}. */ public class SemanticHighlightingManager_NEW implements AnalysisServerHighlightsListener, ITextPresentationListener { /** * Semantic highlighting position updater. */ private class HighlightingPositionUpdater implements IPositionUpdater { @Override public void update(DocumentEvent event) { synchronized (positionsLock) { if (positions == null) { return; } // prepare event values int eventOffset = event.getOffset(); int eventOldLength = event.getLength(); int eventEnd = eventOffset + eventOldLength; // special states { String documentText = document.get(); // check if the same text as for the last highlight regions if (lastText != null && lastText.equals(documentText)) { createPositions(lastRegions); return; } // check if the whole document was replaced (e.g. GIT branch was switched) String eventText = event.getText(); if (eventOffset == 0 && documentText.length() == eventText.length()) { return; } } // update positions for (HighlightPosition position : positions) { int offset = position.getOffset(); int length = position.getLength(); int end = offset + length; if (offset > eventEnd) { updateWithPrecedingEvent(position, event); } else if (end < eventOffset) { updateWithSucceedingEvent(position, event); } else if (offset <= eventOffset && end >= eventEnd) { updateWithIncludedEvent(position, event); } else if (offset <= eventOffset) { updateWithOverEndEvent(position, event); } else if (end >= eventEnd) { updateWithOverStartEvent(position, event); } else { updateWithIncludingEvent(position, event); } } } } private boolean isDartIdentifierPart(String text, int index) { char c = text.charAt(index); return Character.isJavaIdentifierPart(c); } /** * Update the given position with the given event. * <p> * The event is included by the position. */ private void updateWithIncludedEvent(HighlightPosition position, DocumentEvent event) { String eventText = event.getText(); if (eventText != null) { int length = position.getLength(); int newLength = length + eventText.length(); position.setLength(newLength); } } /** * Update the given position with the given event. * <p> * The event includes the position. */ private void updateWithIncludingEvent(HighlightPosition position, DocumentEvent event) { position.delete(); position.update(event.getOffset(), 0); } /** * Update the given position with the given event. * <p> * The event overlaps with the end of the position. */ private void updateWithOverEndEvent(HighlightPosition position, DocumentEvent event) { String newText = event.getText(); if (newText == null) { newText = ""; } int eventNewLength = newText.length(); int includedLength = 0; while (includedLength < eventNewLength && isDartIdentifierPart(newText, includedLength)) { includedLength++; } position.setLength(event.getOffset() - position.getOffset() + includedLength); } /** * Update the given position with the given event. * <p> * The event overlaps with the start of the position. */ private void updateWithOverStartEvent(HighlightPosition position, DocumentEvent event) { int eventOffset = event.getOffset(); int eventEnd = eventOffset + event.getLength(); String newText = event.getText(); if (newText == null) { newText = ""; } int eventNewLength = newText.length(); int excludedLength = eventNewLength; while (excludedLength > 0 && isDartIdentifierPart(newText, excludedLength - 1)) { excludedLength--; } int deleted = eventEnd - position.getOffset(); int inserted = eventNewLength - excludedLength; position.update(eventOffset + excludedLength, position.getLength() - deleted + inserted); } /** * Update the given position with the given event. * <p> * The event precedes the position. */ private void updateWithPrecedingEvent(HighlightPosition position, DocumentEvent event) { String newText = event.getText(); int eventNewLength = newText != null ? newText.length() : 0; int deltaLength = eventNewLength - event.getLength(); position.setOffset(position.getOffset() + deltaLength); } /** * Update the given position with the given event. * <p> * The event succeeds the position. */ private void updateWithSucceedingEvent(HighlightPosition position, DocumentEvent event) { } } /** * A {@link Position} that can be tracked by a {@link Document} and contains the * {@link HighlightRegion}. */ private static class HighlightPosition extends Position { private final HighlightRegion highlight; public HighlightPosition(HighlightRegion highlight) { super(highlight.getOffset(), highlight.getLength()); this.highlight = highlight; } @Override public String toString() { return "[" + super.toString() + " " + highlight + "]"; } public void update(int offset, int len) { setOffset(offset); setLength(len); } } private final DartSourceViewer viewer; private final String file; private final DartReconcilingStrategy reconcilingStrategy; private final IDocument document; private final IPositionUpdater positionUpdater = new HighlightingPositionUpdater(); private final Object positionsLock = new Object(); private HighlightPosition[] positions; private String lastText; private HighlightRegion[] lastRegions; public SemanticHighlightingManager_NEW(DartSourceViewer viewer, String file, DartReconcilingStrategy reconcilingStrategy) { this.viewer = viewer; this.file = file; this.reconcilingStrategy = reconcilingStrategy; this.document = viewer.getDocument(); document.addPositionUpdater(positionUpdater); // subscribe AnalysisServerData analysisServerData = DartCore.getAnalysisServerData(); analysisServerData.addHighlightsListener(file, this); viewer.prependTextPresentationListener(this); } @Override public void applyTextPresentation(TextPresentation textPresentation) { synchronized (positionsLock) { if (positions == null) { return; } // prepare damaged region IRegion damagedRegion = textPresentation.getExtent(); int daOffset = damagedRegion.getOffset(); int daEnd = daOffset + damagedRegion.getLength(); // prepare theme access IPreferenceStore store = DartToolsPlugin.getDefault().getPreferenceStore(); IColorManager colorManager = DartUI.getColorManager(); // add style ranges for (HighlightPosition position : positions) { // skip if outside of the damaged region int hiOffset = position.getOffset(); int hiLength = position.getLength(); int hiEnd = hiOffset + hiLength; if (hiEnd < daOffset || hiOffset >= daEnd) { continue; } if (hiEnd > daEnd) { continue; } // prepare highlight key String highlightType = position.highlight.getType(); String themeKey = getThemeKey(highlightType); if (themeKey == null) { continue; } themeKey = "semanticHighlighting." + themeKey; // prepare color RGB foregroundRGB = PreferenceConverter.getColor(store, themeKey + ".color"); if (foregroundRGB == PreferenceConverter.COLOR_DEFAULT_DEFAULT) { continue; } Color foregroundColor = colorManager.getColor(foregroundRGB); // prepare font style boolean fontBold = store.getBoolean(themeKey + ".bold"); boolean fontItalic = store.getBoolean(themeKey + ".italic"); int fontStyle = 0; if (fontBold) { fontStyle |= SWT.BOLD; } if (fontItalic) { fontStyle |= SWT.ITALIC; } // merge style range textPresentation.replaceStyleRange(new StyleRange( hiOffset, hiLength, foregroundColor, null, fontStyle)); } } } @Override public void computedHighlights(String file, HighlightRegion[] highlights) { if (reconcilingStrategy != null && reconcilingStrategy.hasPendingContentChanges()) { if (!DartCoreDebug.DISABLE_SEMANTIC_HIGHLIGHT_FILTERING) { return; } } // create HighlightPosition(s) createPositions(highlights); // Invalidate presentation. // Delay it, so that in case of the code completion activation, we can display completions // before the semantic highlighting and catch up while user stares at the completion list. Display.getDefault().asyncExec(new Runnable() { @Override public void run() { Display.getDefault().timerExec(5, new Runnable() { @Override public void run() { viewer.invalidateTextPresentation(); } }); } }); } public void dispose() { AnalysisServerData analysisServerData = DartCore.getAnalysisServerData(); analysisServerData.removeHighlightsListener(file, this); viewer.removeTextPresentationListener(this); document.removePositionUpdater(positionUpdater); } private void createPositions(HighlightRegion[] highlights) { HighlightPosition[] newPositions = new HighlightPosition[highlights.length]; for (int i = 0; i < highlights.length; i++) { HighlightRegion highlight = highlights[i]; newPositions[i] = new HighlightPosition(highlight); } // HighlightingPositionUpdater works owns the document lock and the grabs positionsLock. // To avoid dead lock, we must grab lock in the same order - document, then positionsLock. synchronized (getDocumentLockObject()) { synchronized (positionsLock) { lastText = document.get(); lastRegions = highlights; positions = newPositions; } } } /** * Returns the lock to use to synchronize {@link #document} access. */ private Object getDocumentLockObject() { if (document instanceof ISynchronizable) { Object lock = ((ISynchronizable) document).getLockObject(); if (lock != null) { return lock; } } return document; } /** * The type will be a {@link String} from {@link HighlightRegionType}. */ private String getThemeKey(String type) { if (type.equals(HighlightRegionType.ANNOTATION)) { return "annotation"; } else if (type.equals(HighlightRegionType.BUILT_IN) || type.equals(HighlightRegionType.KEYWORD)) { return "builtin"; } else if (type.equals(HighlightRegionType.CLASS)) { return "class"; } else if (type.equals(HighlightRegionType.CONSTRUCTOR)) { return "constructor"; } else if (type.equals(HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_DECLARATION) || type.equals(HighlightRegionType.DYNAMIC_LOCAL_VARIABLE_REFERENCE) || type.equals(HighlightRegionType.DYNAMIC_PARAMETER_DECLARATION) || type.equals(HighlightRegionType.DYNAMIC_PARAMETER_REFERENCE)) { return "dynamicType"; } else if (type.equals(HighlightRegionType.ENUM)) { return "enum"; } else if (type.equals(HighlightRegionType.ENUM_CONSTANT)) { return "enumConstant"; } else if (type.equals(HighlightRegionType.FUNCTION_TYPE_ALIAS)) { return "functionTypeAlias"; } else if (type.equals(HighlightRegionType.INSTANCE_FIELD_DECLARATION) || type.equals(HighlightRegionType.INSTANCE_FIELD_REFERENCE) || type.equals(HighlightRegionType.INSTANCE_GETTER_REFERENCE) || type.equals(HighlightRegionType.INSTANCE_SETTER_REFERENCE)) { return "field"; } else if (type.equals(HighlightRegionType.INSTANCE_GETTER_DECLARATION) || type.equals(HighlightRegionType.STATIC_GETTER_DECLARATION) || type.equals(HighlightRegionType.TOP_LEVEL_GETTER_DECLARATION)) { return "getterDeclaration"; } else if (type.equals(HighlightRegionType.INSTANCE_METHOD_DECLARATION)) { return "methodDeclarationName"; } else if (type.equals(HighlightRegionType.INSTANCE_METHOD_REFERENCE)) { return "method"; } else if (type.equals(HighlightRegionType.INSTANCE_SETTER_DECLARATION) || type.equals(HighlightRegionType.STATIC_SETTER_DECLARATION) || type.equals(HighlightRegionType.TOP_LEVEL_SETTER_DECLARATION)) { return "setterDeclaration"; } else if (type.equals(HighlightRegionType.IMPORT_PREFIX)) { return "importPrefix"; } else if (type.equals(HighlightRegionType.LABEL)) { return "label"; } else if (type.equals(HighlightRegionType.LITERAL_BOOLEAN)) { return "builtin"; } else if (type.equals(HighlightRegionType.LITERAL_DOUBLE) || type.equals(HighlightRegionType.LITERAL_INTEGER)) { return "number"; } else if (type.equals(HighlightRegionType.LITERAL_STRING)) { return "string"; } else if (type.equals(HighlightRegionType.LOCAL_FUNCTION_DECLARATION) || type.equals(HighlightRegionType.TOP_LEVEL_FUNCTION_DECLARATION)) { return "methodDeclarationName"; } else if (type.equals(HighlightRegionType.LOCAL_FUNCTION_REFERENCE) || type.equals(HighlightRegionType.TOP_LEVEL_FUNCTION_REFERENCE)) { return "function"; } else if (type.equals(HighlightRegionType.LOCAL_VARIABLE_DECLARATION)) { return "localVariableDeclaration"; } else if (type.equals(HighlightRegionType.LOCAL_VARIABLE_REFERENCE)) { return "localVariable"; } else if (type.equals(HighlightRegionType.STATIC_FIELD_DECLARATION) || type.equals(HighlightRegionType.STATIC_GETTER_REFERENCE) || type.equals(HighlightRegionType.STATIC_SETTER_REFERENCE)) { return "staticField"; } else if (type.equals(HighlightRegionType.STATIC_METHOD_REFERENCE)) { return "staticMethod"; } else if (type.equals(HighlightRegionType.STATIC_METHOD_DECLARATION)) { return "staticMethodDeclarationName"; } else if (type.equals(HighlightRegionType.PARAMETER_DECLARATION) || type.equals(HighlightRegionType.PARAMETER_REFERENCE)) { return "parameterVariable"; } else if (type.equals(HighlightRegionType.TOP_LEVEL_VARIABLE_DECLARATION) || type.equals(HighlightRegionType.TOP_LEVEL_GETTER_REFERENCE) || type.equals(HighlightRegionType.TOP_LEVEL_SETTER_REFERENCE)) { return "staticField"; } else if (type.equals(HighlightRegionType.TYPE_NAME_DYNAMIC)) { return "builtin"; } else if (type.equals(HighlightRegionType.TYPE_PARAMETER)) { return "typeParameter"; } else { // unsupported: // COMMENT_BLOCK: // COMMENT_DOCUMENTATION: // COMMENT_END_OF_LINE: // DIRECTIVE: // IDENTIFIER_DEFAULT: // INVALID_STRING_ESCAPE: // LITERAL_LIST: // LITERAL_MAP: // UNRESOLVED_INSTANCE_MEMBER_REFERENCE: // VALID_STRING_ESCAPE: return null; } } }