// 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.workspace.outline; import static com.google.collide.client.workspace.outline.OutlineNode.OUTLINE_NODE_ANCHOR_TYPE; import static com.google.collide.client.workspace.outline.OutlineNode.OutlineNodeType.CSS_CLASS; import static com.google.collide.shared.document.anchor.AnchorManager.IGNORE_LINE_NUMBER; import com.google.collide.client.documentparser.AsyncParser; import com.google.collide.client.documentparser.DocumentParser; import com.google.collide.codemirror2.CssToken; import com.google.collide.codemirror2.Token; import com.google.collide.codemirror2.TokenType; import com.google.collide.json.shared.JsonArray; import com.google.collide.shared.document.Line; import com.google.collide.shared.document.anchor.Anchor; import com.google.collide.shared.document.anchor.AnchorManager; import com.google.collide.shared.util.JsonCollections; import com.google.collide.shared.util.ListenerRegistrar; import com.google.common.base.Preconditions; /** * Parser for CSS files. * * Consumes tokens from codemirror2, produces OutlineNodes. */ public class CssOutlineParser extends AsyncParser<OutlineNode> implements OutlineParser { /** * Parent object that is notified, when parsing is complete. */ private final OutlineConsumer consumer; /** * Root node. * * Root node is created only once, updated by model. */ private final OutlineNode root; /** * Anchor that denotes a first position where we get "tag" or "atom" token. */ private Anchor tagAnchor; /** * Outline node name represented as array of strings. */ private JsonArray<String> tagName; /** * Flag that indicates, that there were unused space token. */ private boolean spaceBetween; /** * Handle for listener unregistration. */ private ListenerRegistrar.Remover listenerRemover; @Override public void onParseLine(Line line, int lineNumber, JsonArray<Token> tokens) { int column = 0; for (Token token : tokens.asIterable()) { TokenType type = token.getType(); String value = token.getValue(); column = column + value.length(); if (TokenType.WHITESPACE == type || TokenType.NEWLINE == type) { spaceBetween = true; continue; } if (TokenType.COMMENT == type || TokenType.VARIABLE == type || TokenType.NUMBER == type) { continue; } Preconditions.checkState(token instanceof CssToken, "Expected CssToken, but received %s", token); String context = ((CssToken) token).getContext(); boolean freeContext = context == null || "@media{".equals(context); if (!freeContext || ",".equals(value) || "}".equals(value) || "{".equals(value)) { // Node is finished, push it to the list. if (tagAnchor != null) { OutlineNode item = new OutlineNode(tagName.join(""), CSS_CLASS, root, lineNumber, 0); addData(item); item.setEnabled(true); item.setAnchor(tagAnchor); tagAnchor = null; tagName.clear(); } spaceBetween = false; } else { if (tagAnchor == null) { tagAnchor = line.getDocument().getAnchorManager().createAnchor( OUTLINE_NODE_ANCHOR_TYPE, line, IGNORE_LINE_NUMBER, column - value.length()); } if (spaceBetween && tagName.size() > 0) { tagName.add(" "); } spaceBetween = false; tagName.add(value); } } spaceBetween = true; } @Override public void onAfterParse(JsonArray<OutlineNode> nodes) { consumer.onOutlineParsed(nodes); } @Override public void onBeforeParse() { // TODO: restore tagName and tagAnchor tagName.clear(); spaceBetween = false; detachLastAnchor(); } private void detachLastAnchor() { if (tagAnchor != null) { if (tagAnchor.isAttached()) { tagAnchor.getLine().getDocument().getAnchorManager().removeAnchor(tagAnchor); } tagAnchor = null; } } @Override public void onCleanup(JsonArray<OutlineNode> nodes) { final int l = nodes.size(); if (l > 0) { AnchorManager anchorManager = nodes.get(0).getAnchor().getLine().getDocument().getAnchorManager(); for (int i = 0; i < l; i++) { Anchor anchor = nodes.get(i).getAnchor(); if (anchor.isAttached()) { anchorManager.removeAnchor(anchor); } } } } @Override public void cleanup() { super.cleanup(); detachLastAnchor(); listenerRemover.remove(); listenerRemover = null; } public CssOutlineParser(ListenerRegistrar<DocumentParser.Listener> parserListenerRegistrar, OutlineConsumer consumer) { this.consumer = consumer; listenerRemover = parserListenerRegistrar.add(this); root = new OutlineNode("css-root", OutlineNode.OutlineNodeType.ROOT, null, 0, 0); tagName = JsonCollections.createArray(); } @Override public OutlineNode getRoot() { return root; } }