blob: 910df9707802dec6ba3f51f647a5b975b1a65d39 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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 org.netbeans.modules.javascript2.editor.formatter;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.TreeMap;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.swing.text.BadLocationException;
import org.netbeans.api.annotations.common.CheckForNull;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
/**
*
* @author Petr Hejl
*/
public final class FormatTokenStream implements Iterable<FormatToken> {
private static final Logger LOGGER = Logger.getLogger(FormatTokenStream.class.getName());
private final TreeMap<Integer, FormatToken> tokenPosition = new TreeMap<>();
private final Map<FormatToken, Integer> originalIndents = new HashMap<>();
private FormatToken firstToken;
private FormatToken lastToken;
private FormatTokenStream() {
super();
}
public static FormatTokenStream create(Context context, TokenSequence<? extends JsTokenId> ts,
int start, int end) {
FormatTokenStream ret = new FormatTokenStream();
int diff = ts.move(start);
if (diff <= 0) {
ts.movePrevious();
}
ret.addToken(FormatToken.forFormat(FormatToken.Kind.SOURCE_START));
while (ts.moveNext() && ts.offset() < end) {
Token<? extends JsTokenId> token = ts.token();
JsTokenId id = token.id();
switch (id) {
case EOL:
ret.addToken(FormatToken.forAny(FormatToken.Kind.EOL, ts.offset(), token.text(), id));
break;
case WHITESPACE:
ret.addToken(FormatToken.forAny(FormatToken.Kind.WHITESPACE, ts.offset(), token.text(), id));
break;
case BLOCK_COMMENT:
ret.addToken(FormatToken.forAny(FormatToken.Kind.BLOCK_COMMENT, ts.offset(), token.text(), id));
break;
case DOC_COMMENT:
ret.addToken(FormatToken.forAny(FormatToken.Kind.DOC_COMMENT, ts.offset(), token.text(), id));
break;
case LINE_COMMENT:
ret.addToken(FormatToken.forAny(FormatToken.Kind.LINE_COMMENT, ts.offset(), token.text(), id));
break;
case OPERATOR_GREATER:
case OPERATOR_LOWER:
case OPERATOR_EQUALS:
case OPERATOR_EQUALS_EXACTLY:
case OPERATOR_LOWER_EQUALS:
case OPERATOR_GREATER_EQUALS:
case OPERATOR_NOT_EQUALS:
case OPERATOR_NOT_EQUALS_EXACTLY:
case OPERATOR_AND:
case OPERATOR_OR:
case OPERATOR_EXPONENTIATION:
case OPERATOR_MULTIPLICATION:
case OPERATOR_DIVISION:
case OPERATOR_BITWISE_AND:
case OPERATOR_BITWISE_OR:
case OPERATOR_BITWISE_XOR:
case OPERATOR_MODULUS:
case OPERATOR_LEFT_SHIFT_ARITHMETIC:
case OPERATOR_RIGHT_SHIFT_ARITHMETIC:
case OPERATOR_RIGHT_SHIFT:
case OPERATOR_PLUS:
case OPERATOR_MINUS:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_BINARY_OPERATOR_WRAP));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_BINARY_OPERATOR));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_BINARY_OPERATOR));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_BINARY_OPERATOR_WRAP));
break;
case OPERATOR_ASSIGNMENT:
case OPERATOR_PLUS_ASSIGNMENT:
case OPERATOR_MINUS_ASSIGNMENT:
case OPERATOR_EXPONENTIATION_ASSIGNMENT:
case OPERATOR_MULTIPLICATION_ASSIGNMENT:
case OPERATOR_DIVISION_ASSIGNMENT:
case OPERATOR_BITWISE_AND_ASSIGNMENT:
case OPERATOR_BITWISE_OR_ASSIGNMENT:
case OPERATOR_BITWISE_XOR_ASSIGNMENT:
case OPERATOR_MODULUS_ASSIGNMENT:
case OPERATOR_LEFT_SHIFT_ARITHMETIC_ASSIGNMENT:
case OPERATOR_RIGHT_SHIFT_ARITHMETIC_ASSIGNMENT:
case OPERATOR_RIGHT_SHIFT_ASSIGNMENT:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_ASSIGNMENT_OPERATOR));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_ASSIGNMENT_OPERATOR));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_ASSIGNMENT_OPERATOR_WRAP));
break;
case OPERATOR_ARROW:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_ARROW_OPERATOR));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_ARROW_OPERATOR));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_ARROW_OPERATOR_WRAP));
break;
case OPERATOR_COMMA:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_COMMA));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_COMMA));
break;
case OPERATOR_OPTIONAL_ACCESS:
case OPERATOR_DOT:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_DOT));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_DOT));
break;
case KEYWORD_IF:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_IF_KEYWORD));
break;
case KEYWORD_WHILE:
// we do not put before here, we do it in visitor to put it
// only for do while
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_WHILE_KEYWORD));
break;
case KEYWORD_FOR:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_FOR_KEYWORD));
break;
case KEYWORD_WITH:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_WITH_KEYWORD));
break;
case KEYWORD_SWITCH:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_SWITCH_KEYWORD));
break;
case KEYWORD_CATCH:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_CATCH_KEYWORD));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_CATCH_KEYWORD));
break;
case KEYWORD_ELSE:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_ELSE_KEYWORD));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
break;
case KEYWORD_FINALLY:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_FINALLY_KEYWORD));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
break;
case KEYWORD_VAR:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_VAR_KEYWORD));
break;
case KEYWORD_NEW:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_NEW_KEYWORD));
break;
case KEYWORD_TYPEOF:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_TYPEOF_KEYWORD));
break;
case OPERATOR_SEMICOLON:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_SEMICOLON));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_SEMICOLON));
break;
case BRACKET_LEFT_PAREN:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
ret.addToken(FormatToken.forFormat(FormatToken.Kind.AFTER_LEFT_PARENTHESIS));
break;
case BRACKET_RIGHT_PAREN:
ret.addToken(FormatToken.forFormat(FormatToken.Kind.BEFORE_RIGHT_PARENTHESIS));
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
break;
case JSX_TEXT:
FormatToken jsxToken = FormatToken.forText(ts.offset(), token.text(), id);
if (context != null) {
try {
int indent = context.lineIndent(context.lineStartOffset(ts.offset()));
ret.setOriginalIndent(jsxToken, indent);
} catch (BadLocationException ex) {
LOGGER.log(Level.INFO, null, ex);
}
}
ret.addToken(jsxToken);
break;
default:
ret.addToken(FormatToken.forText(ts.offset(), token.text(), id));
break;
}
}
return ret;
}
@CheckForNull
public static FormatToken getNextNonVirtual(FormatToken token) {
FormatToken current = token.next();
while (current != null && current.isVirtual()) {
current = current.next();
}
return current;
}
@CheckForNull
public static FormatToken getNextNonWhite(FormatToken token, boolean allowEol) {
FormatToken current = token.next();
while (current != null && (current.isVirtual()
|| current.getKind() == FormatToken.Kind.WHITESPACE
|| (allowEol && current.getKind() == FormatToken.Kind.EOL))) {
current = current.next();
}
return current;
}
@CheckForNull
public static FormatToken getNextImportant(FormatToken token) {
FormatToken current = token.next();
while (current != null && (current.isVirtual()
|| current.getKind() == FormatToken.Kind.WHITESPACE
|| current.getKind() == FormatToken.Kind.EOL
|| current.getKind() == FormatToken.Kind.DOC_COMMENT
|| current.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| current.getKind() == FormatToken.Kind.LINE_COMMENT)) {
current = current.next();
}
return current;
}
@CheckForNull
public static FormatToken getPreviousNonVirtual(FormatToken token) {
FormatToken current = token.previous();
while (current != null && current.isVirtual()) {
current = current.previous();
}
return current;
}
public FormatToken getToken(int offset) {
return tokenPosition.get(offset);
}
/**
* Returns token containing the offset if any. Returned token does not
* have to start at the offset but it has to cover it.
* @param offset the give offset
* @return token containing the offset or <code>null</code>
*/
public FormatToken getCoveringToken(int offset) {
Map.Entry<Integer, FormatToken> entry = tokenPosition.floorEntry(offset);
if (entry != null) {
FormatToken token = entry.getValue();
if (!token.isVirtual()) {
if (token.getOffset() == offset) {
return token;
}
int endPos = token.getOffset() + token.getText().length();
if (offset >= token.getOffset() && offset < endPos) {
return token;
}
}
}
return null;
}
@Override
public Iterator<FormatToken> iterator() {
return new FormatTokenIterator();
}
public List<FormatToken> getTokens() {
List<FormatToken> tokens = new ArrayList<>((int) (tokenPosition.size() * 1.5));
for (FormatToken token : this) {
tokens.add(token);
}
return tokens;
}
public void addToken(FormatToken token) {
if (firstToken == null) {
firstToken = token;
lastToken = token;
} else {
lastToken.setNext(token);
token.setPrevious(lastToken);
lastToken = token;
}
if (token.getOffset() >= 0) {
tokenPosition.put(token.getOffset(), token);
}
}
public void removeToken(FormatToken token) {
assert token.isVirtual() : token;
FormatToken previous = token.previous();
FormatToken next = token.next();
token.setNext(null);
token.setPrevious(null);
if (token.getOffset() >= 0) {
tokenPosition.remove(token.getOffset());
}
if (previous == null) {
assert firstToken == token;
firstToken = next;
next.setPrevious(null);
}
if (next == null) {
assert lastToken == token;
lastToken = previous;
previous.setNext(null);
}
if (previous == null || next == null) {
return;
}
previous.setNext(next);
next.setPrevious(previous);
}
public void setOriginalIndent(FormatToken token, int indent) {
originalIndents.put(token, indent);
}
public Integer getOriginalIndent(FormatToken token) {
return originalIndents.get(token);
}
private class FormatTokenIterator implements Iterator<FormatToken> {
private FormatToken current = firstToken;
@Override
public boolean hasNext() {
return current != null;
}
@Override
public FormatToken next() {
if (!hasNext()) {
throw new NoSuchElementException();
}
FormatToken ret = current;
current = current.next();
return ret;
}
@Override
public void remove() {
throw new UnsupportedOperationException("Remove operation not supported.");
}
}
}