// Copyright 2012 Google Inc. All Rights Reserved. // // 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.google.collide.client.syntaxhighlighter; import com.google.collide.client.documentparser.DocumentParser; import com.google.collide.client.editor.Editor; import com.google.collide.client.editor.ViewportModel; import com.google.collide.client.editor.renderer.LineRenderer; import com.google.collide.client.editor.renderer.Renderer; import com.google.collide.client.editor.selection.SelectionModel; import com.google.collide.codemirror2.Token; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.document.Document; import com.google.collide.shared.document.Line; import com.google.collide.shared.util.JsonCollections; import com.google.collide.shared.util.ListenerRegistrar; import javax.annotation.Nonnull; /** * Syntax highlighter for the Collide editor. * */ public class SyntaxHighlighter implements DocumentParser.Listener, Renderer.CompletionListener { /** * Key for {@link Line#getTag} that stores the parsed tokens for that line. We * must cache these because of the asynchronous nature of rendering. Once the * rendering pass is complete, we clear this cache. So, this cache gets cleared * before the browser event loop is run. */ private static final String LINE_TAG_CACHED_TOKENS = "SyntaxHighlighter.cachedTokens"; public static SyntaxHighlighter create(Document document, Renderer renderer, ViewportModel viewport, SelectionModel selection, DocumentParser documentParser, Editor.Css editorCss) { ListenerRegistrar.RemoverManager removerManager = new ListenerRegistrar.RemoverManager(); SyntaxHighlighter syntaxHighlighter = new SyntaxHighlighter(document, renderer, viewport, selection, documentParser, removerManager, editorCss); removerManager.track(documentParser.getListenerRegistrar().add(syntaxHighlighter)); removerManager.track(renderer.getCompletionListenerRegistrar().add(syntaxHighlighter)); return syntaxHighlighter; } private final Renderer editorRenderer; private final SyntaxHighlighterRenderer lineRenderer; private final ViewportModel viewport; private final JsonArray<Line> linesWithCachedTokens; private final DocumentParser documentParser; private final ListenerRegistrar.RemoverManager removerManager; private SyntaxHighlighter(Document document, Renderer editorRenderer, ViewportModel viewport, SelectionModel selection, DocumentParser documentParser, ListenerRegistrar.RemoverManager removerManager, Editor.Css editorCss) { this.editorRenderer = editorRenderer; this.viewport = viewport; this.documentParser = documentParser; this.removerManager = removerManager; this.linesWithCachedTokens = JsonCollections.createArray(); this.lineRenderer = new SyntaxHighlighterRenderer(this, selection, editorCss); } public LineRenderer getRenderer() { return lineRenderer; } @Override public void onIterationStart(int lineNumber) { // do nothing } @Override public void onIterationFinish() { // do nothing } @Override public void onDocumentLineParsed(Line line, int lineNumber, @Nonnull JsonArray<Token> tokens) { if (!viewport.isLineInViewport(line)) { return; } // Save the cached tokens so the async render will have them accessible line.putTag(LINE_TAG_CACHED_TOKENS, tokens); linesWithCachedTokens.add(line); editorRenderer.requestRenderLine(line); } @Override public void onRenderCompleted() { // Wipe the cached tokens for (int i = 0, n = linesWithCachedTokens.size(); i < n; i++) { linesWithCachedTokens.get(i).putTag(LINE_TAG_CACHED_TOKENS, null); } linesWithCachedTokens.clear(); } public void teardown() { removerManager.remove(); } /** * Returns the tokens for the given line, or null if the tokens could not be * retrieved synchronously */ JsonArray<Token> getTokens(Line line) { JsonArray<Token> tokens = line.getTag(LINE_TAG_CACHED_TOKENS); /* * If we haven't gotten a callback from the parser (hence no cached tokens), * try to synchronously parse the line */ return tokens != null ? tokens : documentParser.parseLineSync(line); } }