Java tutorial
/* * Copyright 2013 Palantir Technologies, Inc. * * Licensed under the Apache License, Version 2.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.apache.org/licenses/LICENSE-2.0 * * 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.palantir.typescript.text; import static com.google.common.base.Preconditions.checkNotNull; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.jface.text.BadLocationException; import org.eclipse.jface.text.IDocument; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextListener; import org.eclipse.jface.text.ITextViewer; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.TextAttribute; import org.eclipse.jface.text.TextEvent; import org.eclipse.jface.text.TextPresentation; import org.eclipse.jface.text.presentation.IPresentationDamager; import org.eclipse.jface.text.presentation.IPresentationReconciler; import org.eclipse.jface.text.presentation.IPresentationRepairer; import org.eclipse.swt.SWT; import org.eclipse.swt.custom.StyleRange; import org.eclipse.swt.graphics.Color; import com.google.common.base.Supplier; import com.google.common.base.Suppliers; import com.google.common.collect.ImmutableMap; import com.google.common.collect.Lists; import com.google.common.collect.Maps; import com.palantir.typescript.Colors; import com.palantir.typescript.services.classifier.ClassificationInfo; import com.palantir.typescript.services.classifier.ClassificationResult; import com.palantir.typescript.services.classifier.Classifier; import com.palantir.typescript.services.classifier.EndOfLineState; import com.palantir.typescript.services.classifier.TokenClass; /** * The presentation reconciler performs syntax highlighting. * * @author dcicerone */ public final class PresentationReconciler implements IPresentationReconciler { private static final Supplier<Classifier> CLASSIFIER_SUPPLIER = Suppliers.memoize(new Supplier<Classifier>() { @Override public Classifier get() { return new Classifier(); } }); private final ImmutableMap<TokenClass, TextAttribute> classificationTextAttributes; private final Map<Integer, EndOfLineState> finalLexStates; private Classifier classifier; private ITextListener listener; private ITextViewer viewer; public PresentationReconciler() { this.classifier = CLASSIFIER_SUPPLIER.get(); this.classificationTextAttributes = createClassificationTextAttributes(); this.listener = new MyTextListener(); this.finalLexStates = Maps.newTreeMap(); } @Override public void install(ITextViewer textViewer) { checkNotNull(textViewer); this.viewer = textViewer; this.viewer.addTextListener(this.listener); // do the initial syntax highlighting IDocument document = this.viewer.getDocument(); if (document != null) { Region region = new Region(0, document.getLength()); TextPresentation presentation = this.createPresentation(region, null); this.viewer.changeTextPresentation(presentation, false); } } @Override public void uninstall() { this.viewer.removeTextListener(this.listener); this.viewer = null; } @Override public IPresentationDamager getDamager(String contentType) { throw new UnsupportedOperationException(); } @Override public IPresentationRepairer getRepairer(String contentType) { throw new UnsupportedOperationException(); } /** * Processes the text event and updates the syntax highlighting as necessary. */ private void processEvent(TextEvent event) { IRegion damagedRegion = this.getDamagedRegion(event); EndOfLineState lastDamagedLexState = this.updateFinalLexStates(event, damagedRegion); TextPresentation presentation = this.createPresentation(damagedRegion, lastDamagedLexState); this.viewer.changeTextPresentation(presentation, false); } /** * Gets the damaged region by selecting the whole lines touched by the text edit. */ private IRegion getDamagedRegion(TextEvent event) { IDocument document = this.viewer.getDocument(); int documentLength = document.getLength(); int offset = event.getOffset(); int length = event.getLength(); String text = event.getText(); // redraw state change - re-classify the entire document if (event.getDocumentEvent() == null && offset == 0 && length == 0) { this.finalLexStates.clear(); return new Region(0, documentLength); } try { IRegion startLineInfo = document.getLineInformationOfOffset(offset); int startOffset = startLineInfo.getOffset(); int endOffset = offset + (text != null ? text.length() : length); int firstLineEndOffset = startOffset + startLineInfo.getLength(); if (startOffset <= endOffset && endOffset <= firstLineEndOffset) { // single line damaged: extend the damaged region to the end of the first line endOffset = firstLineEndOffset; } else { // multiple lines damaged IRegion endLineInfo = document.getLineInformationOfOffset(endOffset); int lastLineEndOffset = endLineInfo.getOffset() + endLineInfo.getLength(); // extend the damaged region to the end of last line endOffset = lastLineEndOffset; } return new Region(startOffset, endOffset - startOffset); } catch (BadLocationException e) { throw new RuntimeException(e); } } private EndOfLineState updateFinalLexStates(TextEvent event, IRegion damagedRegion) { EndOfLineState lastDamagedLexState = null; if (!this.finalLexStates.isEmpty()) { int offset = event.getOffset(); int oldLength = event.getLength(); String text = event.getText(); int newLength = text != null ? text.length() : 0; int damagedOffset = damagedRegion.getOffset(); int damagedLength = oldLength + offset - damagedOffset; int delta = newLength - oldLength; // determine which final lex states are impacted and remove them or update them as appropriate Map<Integer, EndOfLineState> newFinalLexStates = Maps.newHashMap(); Iterator<Entry<Integer, EndOfLineState>> it = this.finalLexStates.entrySet().iterator(); while (it.hasNext()) { Entry<Integer, EndOfLineState> entry = it.next(); Integer entryOffset = entry.getKey(); // entry is after the beginning of the damaged region if (entryOffset >= damagedOffset) { EndOfLineState lexState = entry.getValue(); if (entryOffset <= damagedOffset + damagedLength) { // remove the entry but keep track of its lex state it.remove(); lastDamagedLexState = lexState; } else if (delta != 0) { // update the entry since it occurs after the damaged area it.remove(); newFinalLexStates.put(entryOffset + delta, lexState); } } } this.finalLexStates.putAll(newFinalLexStates); } return lastDamagedLexState; } private TextPresentation createPresentation(IRegion damagedRegion, EndOfLineState lastDamagedLexState) { TextPresentation presentation = new TextPresentation(damagedRegion, 1000); IDocument document = this.viewer.getDocument(); int offset = damagedRegion.getOffset(); int length = damagedRegion.getLength(); try { int startLine = document.getLineOfOffset(offset); int endLine = document.getLineOfOffset(offset + length); this.classifyLines(presentation, startLine, endLine, lastDamagedLexState); } catch (BadLocationException e) { throw new RuntimeException(e); } return presentation; } private void classifyLines(TextPresentation presentation, int startLine, int endLine, EndOfLineState lastDamagedLexState) throws BadLocationException { IDocument document = this.viewer.getDocument(); // get the lines List<String> lines = Lists.newArrayList(); for (int i = startLine; i <= endLine; i++) { IRegion lineInfo = document.getLineInformation(i); String line = document.get(lineInfo.getOffset(), lineInfo.getLength()); lines.add(line); } // get the previous line's final lex state (if available) EndOfLineState lexState = EndOfLineState.START; if (startLine > 0 && !this.finalLexStates.isEmpty()) { int previousLineOffset = document.getLineOffset(startLine - 1); lexState = this.finalLexStates.get(previousLineOffset); } boolean lastLexStateDiffers = false; List<ClassificationResult> results = this.classifier.getClassificationsForLines(lines, lexState); for (int i = 0; i < results.size(); i++) { int line = startLine + i; int lineOffset = document.getLineOffset(line); ClassificationResult result = results.get(i); EndOfLineState finalLexState = result.getFinalLexState(); // add the style ranges for the classified text int currentOffset = lineOffset; for (ClassificationInfo entry : result.getEntries()) { TokenClass classification = entry.getClassification(); int length = entry.getLength(); this.addStyleRange(presentation, currentOffset, length, classification); currentOffset += length; } // store the new final lex state this.finalLexStates.put(lineOffset, finalLexState); lastLexStateDiffers = (lastDamagedLexState != null && finalLexState != lastDamagedLexState); } // re-classify the rest of the lines in the document if the last damaged lex state and last repaired lex state are different if (lastLexStateDiffers) { int lastLine = document.getLineOfOffset(document.getLength()); if (endLine < lastLine) { this.classifyLines(presentation, endLine + 1, lastLine, null); } } } private void addStyleRange(TextPresentation presentation, int offset, int length, TokenClass classification) { TextAttribute textAttribute = this.classificationTextAttributes.get(classification); Color foreground = textAttribute.getForeground(); Color background = textAttribute.getBackground(); int fontStyle = textAttribute.getStyle(); StyleRange styleRange = new StyleRange(offset, length, foreground, background, fontStyle); presentation.addStyleRange(styleRange); } private static ImmutableMap<TokenClass, TextAttribute> createClassificationTextAttributes() { ImmutableMap.Builder<TokenClass, TextAttribute> classAttributes = ImmutableMap.builder(); classAttributes.put(TokenClass.COMMENT, new TextAttribute(Colors.getColor(Colors.COMMENT))); classAttributes.put(TokenClass.IDENTIFIER, new TextAttribute(Colors.getColor(Colors.IDENTIFIER))); classAttributes.put(TokenClass.KEYWORD, new TextAttribute(Colors.getColor(Colors.KEYWORD), null, SWT.BOLD)); classAttributes.put(TokenClass.NUMBER_LITERAL, new TextAttribute(Colors.getColor(Colors.NUMBER_LITERAL))); classAttributes.put(TokenClass.OPERATOR, new TextAttribute(Colors.getColor(Colors.OPERATOR))); classAttributes.put(TokenClass.PUNCTUATION, new TextAttribute(Colors.getColor(Colors.PUNCTUATION))); classAttributes.put(TokenClass.REG_EXP_LITERAL, new TextAttribute(Colors.getColor(Colors.REG_EXP_LITERAL))); classAttributes.put(TokenClass.STRING_LITERAL, new TextAttribute(Colors.getColor(Colors.STRING_LITERAL))); classAttributes.put(TokenClass.WHITESPACE, new TextAttribute(Colors.getColor(Colors.WHITESPACE))); return classAttributes.build(); } private final class MyTextListener implements ITextListener { @Override public void textChanged(TextEvent event) { PresentationReconciler.this.processEvent(event); } } }