blob: 4cc67d995447e2f5943a3fdacfc1b5eeb46087fe [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 com.oracle.js.parser.ir.FunctionNode;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.regex.Pattern;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import org.netbeans.api.annotations.common.NonNull;
import org.netbeans.api.annotations.common.NullAllowed;
import org.netbeans.api.editor.document.AtomicLockDocument;
import org.netbeans.api.editor.document.LineDocument;
import org.netbeans.api.editor.document.LineDocumentUtils;
import org.netbeans.api.lexer.Language;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenId;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.modules.csl.api.Formatter;
import org.netbeans.modules.csl.api.OffsetRange;
import org.netbeans.modules.csl.spi.GsfUtilities;
import org.netbeans.modules.csl.spi.ParserResult;
import org.netbeans.modules.editor.indent.api.IndentUtils;
import org.netbeans.modules.editor.indent.spi.Context;
import org.netbeans.modules.javascript2.json.parser.JsonParser;
import org.netbeans.modules.javascript2.lexer.api.JsTokenId;
import org.netbeans.modules.javascript2.lexer.api.LexUtilities;
import org.netbeans.modules.parsing.api.Snapshot;
import org.openide.util.Exceptions;
/**
*
* @author Petr Hejl
*/
public class JsFormatter implements Formatter {
// only for tests
static final Object CT_HANDLER_DOC_PROPERTY = "code-template-insert-handler"; // NOI18N
private static final Logger LOGGER = Logger.getLogger(JsFormatter.class.getName());
private static final Pattern MOOTOOLS_COMMENT = Pattern.compile("/\\*\n---\\s*\n((.|\n)+)\n\\.\\.\\.\\s*\n\\*/");
private static final boolean ELSE_IF_SINGLE_LINE = true;
private final Language<JsTokenId> language;
private final Defaults.Provider provider;
private int lastOffsetDiff = 0;
private final Set<FormatToken> processed = new HashSet<>();
private final Stack<FormatToken> openedBraces = new Stack<>();
public JsFormatter(Language<JsTokenId> language) {
this.language = language;
this.provider = Defaults.getInstance(language.mimeType());
}
@Override
public int hangingIndentSize() {
return CodeStyle.get((Document) null, provider).getContinuationIndentSize();
}
@Override
public int indentSize() {
return CodeStyle.get((Document) null, provider).getIndentSize();
}
@Override
public boolean needsParserResult() {
return true;
}
@Override
public void reformat(final Context context, final ParserResult compilationInfo) {
processed.clear();
lastOffsetDiff = 0;
final Document doc = context.document();
final boolean templateEdit = doc.getProperty(CT_HANDLER_DOC_PROPERTY) != null;
AtomicLockDocument ald = LineDocumentUtils.as(doc, AtomicLockDocument.class);
@SuppressWarnings("Convert2Lambda") // Converting this to lambda causes class loading issues
Runnable r = new Runnable() {
@Override
public void run() {
long startTime = System.nanoTime();
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
compilationInfo.getSnapshot().getTokenHierarchy(), context.startOffset(), language);
if (ts == null) {
return;
}
FormatTokenStream tokenStream = FormatTokenStream.create(
context, ts, 0, context.endOffset());
LOGGER.log(Level.FINE, "Format token stream creation: {0} ms",
(System.nanoTime() - startTime) / 1000000);
startTime = System.nanoTime();
JsFormatVisitor visitor = new JsFormatVisitor(tokenStream,
ts, context.endOffset());
if (!(compilationInfo instanceof org.netbeans.modules.javascript2.types.spi.ParserResult)) {
throw new IllegalArgumentException(String.valueOf(compilationInfo));
}
org.netbeans.modules.javascript2.types.spi.ParserResult result
= (org.netbeans.modules.javascript2.types.spi.ParserResult) compilationInfo;
final FormatContext formatContext;
final JsonParser.JsonContext json = result.getLookup().lookup(JsonParser.JsonContext.class);
if (json != null) {
JsonFormatVisitor jsonVisitor = new JsonFormatVisitor(tokenStream, ts, context.endOffset());
json.accept(jsonVisitor);
formatContext = new FormatContext(context, provider, compilationInfo.getSnapshot(),
language, null, tokenStream);
} else {
final FunctionNode fun = result.getLookup().lookup(FunctionNode.class);
if (fun != null) {
fun.accept(visitor);
} else {
LOGGER.log(Level.FINE, "Format visitor not executed; no root node");
}
formatContext = new FormatContext(context, provider, compilationInfo.getSnapshot(),
language, fun, tokenStream);
}
LOGGER.log(Level.FINE, "Format visitor: {0} ms",
(System.nanoTime() - startTime) / 1000000);
startTime = System.nanoTime();
CodeStyle.Holder codeStyle = CodeStyle.get(formatContext).toHolder();
int indentLevelSize = codeStyle.indentSize;
int initialIndent = codeStyle.initialIndent;
int continuationIndent = codeStyle.continuationIndentSize;
List<FormatToken> tokens = tokenStream.getTokens();
if (LOGGER.isLoggable(Level.FINE)) {
for (FormatToken token : tokens) {
LOGGER.log(Level.FINE, token.toString());
}
}
// when the start offset != 0 this flag indicates when we
// got after the start so we may change document now
boolean started = false;
boolean firstTokenFound = false;
Stack<FormatContext.ContinuationBlock> continuations = new Stack<>();
// clear the stack of currently opened braces
openedBraces.clear();
for (int i = 0; i < tokens.size(); i++) {
FormatToken token = tokens.get(i);
if (!started && !token.isVirtual() && token.getOffset() >= context.startOffset()) {
started = true;
}
// only if not expanding tabs otherwise the real size is
// already included in offset diff
// count real tab length for potential if-long line wrapping
if (!codeStyle.expandTabsToSpaces && !token.isVirtual()) {
if (token.getId() == JsTokenId.WHITESPACE) {
CharSequence text = token.getText();
for (int j = 0; j < text.length(); j++) {
if (text.charAt(j) == '\t') { // NOI18N
formatContext.incTabCount();
}
}
} else if (token.getKind() == FormatToken.Kind.EOL) {
formatContext.resetTabCount();
}
}
boolean tokenProcessed = false;
if (processed.remove(token)) {
// we should format brace according to the user's code style
// else/while/catch/finally keyword placement as well
if (token.getKind().isBraceMarker() || token.getKind().isAlignmentMarker()) {
tokenProcessed = true;
} else {
// we can skip this token, since it has been already processed
// when the last EOL had been reached
continue;
}
}
if (!token.isVirtual()) {
// if there is pending continuation (ususally on the same line)
// such as " .something({..." we need to update
// the continuation data on (, { and [ the same way as
// it would be on EOL
boolean change = false;
if (formatContext.isPendingContinuation() && token.getKind() != FormatToken.Kind.EOL) {
change = updateContinuationStart(formatContext, token, continuations, false);
}
if (!change) {
updateContinuationEnd(formatContext, token, continuations);
}
if (!firstTokenFound) {
firstTokenFound = true;
formatContext.setCurrentLineStart(token.getOffset());
}
// we do not store last things for potentially
// removed/replaced tokens
if (token.getKind() != FormatToken.Kind.WHITESPACE
&& token.getKind() != FormatToken.Kind.EOL) {
lastOffsetDiff = formatContext.getOffsetDiff();
}
initialIndent = formatContext.getEmbeddingIndent(tokenStream, token)
+ codeStyle.initialIndent + formatContext.getJsxIndentation();
}
if (started && (token.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| token.getKind() == FormatToken.Kind.DOC_COMMENT
|| token.getKind() == FormatToken.Kind.LINE_COMMENT)) {
try {
int indent = context.lineIndent(context.lineStartOffset(
formatContext.getDocumentOffset(token.getOffset()) + formatContext.getOffsetDiff()));
formatComment(token, formatContext, codeStyle, indent);
} catch (BadLocationException ex) {
LOGGER.log(Level.INFO, null, ex);
}
} else if (started && token.getId() == JsTokenId.JSX_TEXT) {
formatJsx(tokens, formatContext, i, codeStyle, initialIndent, continuationIndent, continuations);
} else if (started && token.getKind().isBraceMarker()) {
formatBrace(tokens, i, formatContext, codeStyle, initialIndent, continuationIndent, continuations, tokenProcessed);
} else if (started && token.getKind().isAlignmentMarker()) {
formatKeywordAlignment(tokens, i, formatContext, codeStyle, initialIndent, continuationIndent, continuations);
} else if (started && token.getKind().isSpaceMarker()) {
formatSpace(tokens, i, formatContext, codeStyle);
} else if (started && token.getKind().isLineWrapMarker()) {
formatLineWrap(tokens, i, formatContext, codeStyle, initialIndent,
continuationIndent, continuations);
} else if (token.getKind().isIndentationMarker()
|| token.getKind() == FormatToken.Kind.BEFORE_JSX_BLOCK_START
|| token.getKind() == FormatToken.Kind.AFTER_JSX_BLOCK_END) {
updateIndentationLevel(token, formatContext, codeStyle);
} else if (token.getKind() == FormatToken.Kind.AFTER_END_BRACE) {
if (!openedBraces.isEmpty()
&& getBracePlacement(openedBraces.pop(), codeStyle) == CodeStyle.BracePlacement.NEW_LINE_INDENTED) {
// token representing closing brace of block with "new line indented" indentation
// therefore decrease the indentation
formatContext.decIndentationLevel();
}
} else if (token.getKind() == FormatToken.Kind.SOURCE_START
|| token.getKind() == FormatToken.Kind.EOL) {
if (started && token.getKind() != FormatToken.Kind.SOURCE_START) {
wrapOnEol(tokens, formatContext, i - 1, codeStyle,
initialIndent, continuationIndent, continuations, 0);
}
if (started) {
// remove trailing spaces
removeTrailingSpaces(tokens, i, formatContext, token, templateEdit);
}
if (token.getKind() != FormatToken.Kind.SOURCE_START) {
formatContext.setCurrentLineStart(token.getOffset()
+ 1 + formatContext.getOffsetDiff());
formatContext.setLastLineWrap(null);
}
// following code handles the indentation
// do not do indentation for line comments starting
// at the beginning of the line to support comment/uncomment
FormatToken next = FormatTokenStream.getNextNonVirtual(token);
if (next != null && next.getKind() == FormatToken.Kind.LINE_COMMENT) {
continue;
}
FormatToken indentationStart = null;
FormatToken indentationEnd = null;
// we add tokens to processed to not to process them twice
for (int j = i + 1; j < tokens.size(); j++) {
FormatToken nextToken = tokens.get(j);
if (!nextToken.isVirtual()) {
if (nextToken.getOffset() >= context.startOffset()) {
started = true;
}
if (nextToken.getKind() != FormatToken.Kind.WHITESPACE) {
indentationEnd = nextToken;
if (indentationStart == null) {
indentationStart = nextToken;
}
break;
} else {
if (indentationStart == null) {
indentationStart = nextToken;
}
}
} else {
updateIndentationLevel(nextToken, formatContext, codeStyle);
}
processed.add(nextToken);
}
// if it is code template formatting we want to do
// proper indentation even on a blank line
if (indentationEnd != null
&& (indentationEnd.getKind() != FormatToken.Kind.EOL || templateEdit)) {
indentLine(token, formatContext, codeStyle, continuations, indentationEnd, indentationStart.getOffset(),
initialIndent, indentLevelSize, continuationIndent, started, false, null, null, null);
}
}
}
// if it is end of file yet we have to do wrap if needed
wrapOnEol(tokens, formatContext, tokens.size() - 1, codeStyle,
initialIndent, continuationIndent, continuations, 0);
LOGGER.log(Level.FINE, "Formatting changes: {0} ms", (System.nanoTime() - startTime) / 1000000);
}
};
if (ald == null) {
r.run();
} else {
// review: shouldn't be runAsUser ?
ald.runAtomic(r);
}
}
private void indentLine(@NonNull FormatToken token, @NonNull FormatContext formatContext, @NonNull CodeStyle.Holder codeStyle,
@NonNull Stack<FormatContext.ContinuationBlock> continuations, @NonNull FormatToken indentationEnd, int indentationStartOffset,
int initialIndent, int indentLevelSize, int continuationIndent, boolean started, boolean noRealEol, @NullAllowed Indentation indentation,
@NullAllowed Integer indentLevel, @NullAllowed Integer offsetDiff) {
int indentationSize = initialIndent + (indentLevel != null ? indentLevel : formatContext.getIndentationLevel()) * indentLevelSize;
int continuationLevel = formatContext.getContinuationLevel();
if (isContinuation(formatContext, codeStyle, token, noRealEol)) {
continuationLevel++;
updateContinuationStart(formatContext, token, continuations, true);
} else {
formatContext.setPendingContinuation(false);
}
indentationSize += continuationIndent * continuationLevel;
indentationSize += formatContext.getSuggestedIndentation(indentationEnd);
if (started) {
FormatToken indentationEndPrev = indentationEnd.previous();
// last empty line formatted by html
if (indentationEnd.getId() == JsTokenId.JSX_EXP_END
&& !(indentationEndPrev != null && indentationEndPrev.getKind() == FormatToken.Kind.BEFORE_JSX_BLOCK_START)) {
indentationSize = computeJsxIndentation(formatContext, indentationEnd.getOffset());
}
if (indentationSize >= 0) {
Indentation checked = indentation == null
? checkIndentation(formatContext.getDocument(), token, indentationEnd, formatContext, formatContext.getContext(), indentationSize)
: indentation;
if (offsetDiff != null) {
formatContext.indentLineWithOffsetDiff(indentationStartOffset, indentationSize,
checked, offsetDiff, codeStyle);
} else {
formatContext.indentLine(indentationStartOffset, indentationSize,
checked, codeStyle);
}
}
if (indentationEndPrev != null
&& indentationEndPrev.getKind() == FormatToken.Kind.BEFORE_JSX_BLOCK_START) {
// it has just been updated
formatContext.updateJsxIndentation(indentationEndPrev);
}
}
}
private static void removeTrailingSpaces(List<FormatToken> tokens, int index,
FormatContext formatContext, FormatToken limit, boolean templateEdit) {
// if it is code template we are doing indentation even on a blank line
if (templateEdit && limit.getKind() == FormatToken.Kind.EOL) {
return;
}
FormatToken start = null;
for (int j = index - 1; j >= 0; j--) {
FormatToken nextToken = tokens.get(j);
if (!nextToken.isVirtual()
&& nextToken.getKind() != FormatToken.Kind.WHITESPACE) {
break;
} else {
start = tokens.get(j);
}
}
while (start != null && start != limit) {
if (!start.isVirtual()) {
formatContext.remove(start.getOffset(),
start.getText().length());
}
start = start.next();
}
}
private FormatToken wrapLine(FormatContext formatContext, CodeStyle.Holder codeStyle, FormatContext.LineWrap lastWrap,
int initialIndent, int continuationIndent, Stack<FormatContext.ContinuationBlock> continuations) {
// we dont have to remove trailing spaces as indentation will fix it
formatContext.insertWithOffsetDiff(lastWrap.getToken().getOffset()
+ lastWrap.getToken().getText().length(), "\n", lastWrap.getOffsetDiff()); // NOI18N
// there is + 1 for eol
formatContext.setCurrentLineStart(lastWrap.getToken().getOffset()
+ lastWrap.getToken().getText().length() + 1 + lastWrap.getOffsetDiff());
FormatToken indentationEnd = FormatTokenStream.getNextNonWhite(lastWrap.getToken(), false);
assert indentationEnd != null;
// do the indentation
indentLine(lastWrap.getToken(), formatContext, codeStyle, continuations,
indentationEnd, lastWrap.getToken().getOffset() + lastWrap.getToken().getText().length() + 1,
initialIndent, IndentUtils.indentLevelSize(formatContext.getDocument()), continuationIndent,
true, true, Indentation.ALLOWED, lastWrap.getIndentationLevel(), lastWrap.getOffsetDiff());
formatContext.resetTabCount();
return indentationEnd;
}
private FormatToken wrapOnEol(List<FormatToken> tokens, FormatContext formatContext, int index,
CodeStyle.Holder codeStyle, int initialIndent, int continuationIndent,
Stack<FormatContext.ContinuationBlock> continuations, int segmentAddition) {
// search for token which will be present just before eol
FormatToken tokenBeforeEol = null;
for (int j = index; j >= 0; j--) {
tokenBeforeEol = tokens.get(j);
if (!tokenBeforeEol.isVirtual()) {
break;
}
}
if (tokenBeforeEol.getKind() != FormatToken.Kind.SOURCE_START) {
int segmentLength = tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length()
- formatContext.getCurrentLineStart() + lastOffsetDiff + segmentAddition;
if (segmentLength >= codeStyle.rightMargin) {
FormatContext.LineWrap lastWrap = formatContext.getLastLineWrap();
if (lastWrap != null) {
// wrap it
return wrapLine(formatContext, codeStyle, lastWrap, initialIndent,
continuationIndent, continuations);
}
}
}
return null;
}
private void formatLineWrap(List<FormatToken> tokens, int index, FormatContext formatContext, CodeStyle.Holder codeStyle,
int initialIndent, int continuationIndent, Stack<FormatContext.ContinuationBlock> continuations) {
FormatToken token = tokens.get(index);
CodeStyle.WrapStyle style = getLineWrap(token, formatContext, codeStyle);
CodeStyle.BracePlacement bracePlacement = getBracePlacement(token, codeStyle);
boolean wrapAlignedKeyword = isKeywordOnNewline(token, codeStyle);
if (style == null && bracePlacement == CodeStyle.BracePlacement.PRESERVE_EXISTING && !wrapAlignedKeyword) {
return;
}
if (formatContext.isEmbedded() && token.getKind() == FormatToken.Kind.AFTER_STATEMENT) {
// we are in the embedded code, check whether it is single-line embedding like
// <a href="#" onclick="edit(id); return false;">Edit</a>
if (isSingleLineEmbedding(token, formatContext.getDocument(), formatContext.getSnapshot())) {
// do not wrap line in case of the single-line embedding
return;
}
}
// search for token which will be present after eol
FormatToken tokenAfterEol = token.next();
int startIndex = index;
while (tokenAfterEol != null && tokenAfterEol.getKind() != FormatToken.Kind.EOL
&& tokenAfterEol.getKind() != FormatToken.Kind.TEXT) {
tokenAfterEol = tokenAfterEol.next();
startIndex++;
}
// search for token which will be present just before eol
FormatToken tokenBeforeEol = null;
for (int j = startIndex - 1; j >= 0; j--) {
tokenBeforeEol = tokens.get(j);
if (!tokenBeforeEol.isVirtual()
&& tokenBeforeEol.getKind() != FormatToken.Kind.WHITESPACE) {
break;
}
}
if (tokenBeforeEol != null && tokenBeforeEol.getKind() == FormatToken.Kind.EOL) {
return;
}
// assert we can use the lastOffsetDiff and lastIndentationLevel
assert tokenBeforeEol.getKind() != FormatToken.Kind.WHITESPACE
&& tokenBeforeEol.getKind() != FormatToken.Kind.EOL : tokenBeforeEol;
if (style == CodeStyle.WrapStyle.WRAP_IF_LONG) {
int segmentLength = tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length()
- formatContext.getCurrentLineStart() + lastOffsetDiff;
// compute real tab size
int tabCount = formatContext.getTabCount();
// otherwise the real size is alredy included in offset diff
if (!codeStyle.expandTabsToSpaces) {
segmentLength += (tabCount * codeStyle.tabSize) - tabCount;
}
if (segmentLength >= codeStyle.rightMargin) {
FormatContext.LineWrap lastWrap = formatContext.getLastLineWrap();
if (lastWrap != null
&& (tokenAfterEol.getKind() != FormatToken.Kind.EOL || segmentLength > codeStyle.rightMargin)) {
// we dont have to remove trailing spaces as indentation will fix it
int offsetBeforeChanges = formatContext.getOffsetDiff();
// wrap it
wrapLine(formatContext, codeStyle, lastWrap, initialIndent,
continuationIndent, continuations);
List<FormatToken> eols = getExtendedTokenAfterEol(tokenAfterEol);
FormatToken extendedTokenAfterEol = eols.get(eols.size() - 1);
moveForward(token, extendedTokenAfterEol, formatContext, codeStyle, true);
// we need to mark the current wrap
formatContext.setLastLineWrap(new FormatContext.LineWrap(
tokenBeforeEol, lastOffsetDiff + (formatContext.getOffsetDiff() - offsetBeforeChanges),
formatContext.getIndentationLevel(), formatContext.getContinuationLevel()));
return;
}
// we proceed with wrapping if there is no wrap other than current
// and we are longer than whats allowed
} else {
formatContext.setLastLineWrap(new FormatContext.LineWrap(
tokenBeforeEol, lastOffsetDiff,
formatContext.getIndentationLevel(), formatContext.getContinuationLevel()));
return;
}
}
List<FormatToken> eols = getExtendedTokenAfterEol(tokenAfterEol);
FormatToken extendedTokenAfterEol = eols.get(eols.size() - 1);
// statement like wrap is a bit special at least for now
// we dont remove redundant eols for them
if (tokenAfterEol != null
// there is no eol
&& (tokenAfterEol.getKind() != FormatToken.Kind.EOL
// or there are multiple lines and we are not after statement like token
|| extendedTokenAfterEol != tokenAfterEol && !isStatementWrap(token))) {
// proceed the skipped tokens moving the main loop
moveForward(token, extendedTokenAfterEol, formatContext, codeStyle, true);
if (style != CodeStyle.WrapStyle.WRAP_NEVER
|| bracePlacement == CodeStyle.BracePlacement.NEW_LINE
|| bracePlacement == CodeStyle.BracePlacement.NEW_LINE_INDENTED
|| wrapAlignedKeyword) {
if (tokenAfterEol.getKind() != FormatToken.Kind.EOL) {
// we have to check the line length and wrap if needed
// FIXME duplicated code
int segmentLength = tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length()
- formatContext.getCurrentLineStart() + lastOffsetDiff;
if (segmentLength >= codeStyle.rightMargin) {
FormatContext.LineWrap lastWrap = formatContext.getLastLineWrap();
if (lastWrap != null && tokenAfterEol.getKind() != FormatToken.Kind.EOL) {
// wrap it
wrapLine(formatContext, codeStyle, lastWrap, initialIndent,
continuationIndent, continuations);
}
}
// we dont have to remove trailing spaces as indentation will fix it
formatContext.insert(tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length(), "\n"); // NOI18N
// there is + 1 for eol
formatContext.setCurrentLineStart(tokenBeforeEol.getOffset()
+ tokenBeforeEol.getText().length() + 1);
formatContext.setLastLineWrap(null);
// do the indentation
indentLine(tokenBeforeEol, formatContext, codeStyle, continuations, tokenAfterEol, tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length(),
initialIndent, IndentUtils.indentLevelSize(formatContext.getDocument()), continuationIndent,
true, true, Indentation.ALLOWED, null, null);
}
if (extendedTokenAfterEol != tokenAfterEol) {
if (extendedTokenAfterEol != null) {
FormatToken end = getEndingEol(token, codeStyle, eols, extendedTokenAfterEol);
formatContext.remove(tokenAfterEol.getOffset(),
end.getOffset() - tokenAfterEol.getOffset());
// move to eol to do indentation in next cycle
// it is safe because we know the token to which we move is eol
processed.remove(extendedTokenAfterEol.previous());
} else {
FormatToken last = tokens.get(tokens.size() - 1);
while (last != null && last.isVirtual()) {
last = last.previous();
}
if (last != null) {
formatContext.remove(tokenAfterEol.getOffset(),
last.getOffset() + last.getText().length() - tokenAfterEol.getOffset());
}
}
}
} else {
int start = tokenBeforeEol.getOffset() + tokenBeforeEol.getText().length();
FormatToken endToken = extendedTokenAfterEol;
if (endToken == null) {
// end of file
endToken = tokens.get(tokens.size() - 1);
while (endToken != null && endToken.isVirtual()) {
endToken = endToken.previous();
}
if (endToken != null) {
formatContext.remove(start, endToken.getOffset() + endToken.getText().length() - start);
}
} else if (endToken.getKind() != FormatToken.Kind.EOL) {
// no eol
FormatToken spaceStartToken = tokenBeforeEol.next();
if (spaceStartToken == null) {
spaceStartToken = tokenBeforeEol;
}
if (isSpace(spaceStartToken, formatContext, codeStyle, true, true)) {
formatContext.replace(start, endToken.getOffset() - start, " "); // NOI18N
} else {
formatContext.remove(start, endToken.getOffset() - start);
}
} else if (tokenAfterEol != endToken) {
// multiple eols
formatContext.remove(start, endToken.getOffset() - start);
}
}
}
}
private void formatSpace(List<FormatToken> tokens, int index, FormatContext formatContext, CodeStyle.Holder codeStyle) {
FormatToken token = tokens.get(index);
assert token.isVirtual();
CodeStyle.WrapStyle style = getLineWrap(tokens, index, formatContext, codeStyle, true);
// wrapping will take care of everything
if (style == CodeStyle.WrapStyle.WRAP_ALWAYS) {
return;
}
FormatToken lastEol = null;
FormatToken start = null;
for (FormatToken current = token.previous(); current != null;
current = current.previous()) {
if (!current.isVirtual()) {
if (current.getKind() != FormatToken.Kind.WHITESPACE
&& current.getKind() != FormatToken.Kind.EOL) {
start = current;
break;
} else if (lastEol == null && current.getKind() == FormatToken.Kind.EOL) {
lastEol = current;
}
}
}
if (start == null) {
return;
}
FormatToken end = null;
List<FormatToken> foundEols = null;
for (FormatToken current = token.next(); current != null;
current = current.next()) {
if (!current.isVirtual()) {
if (current.getKind() != FormatToken.Kind.WHITESPACE
&& current.getKind() != FormatToken.Kind.EOL) {
end = current;
break;
} else if (current.getKind() == FormatToken.Kind.EOL) {
if (foundEols == null) {
foundEols = new ArrayList<>(2);
}
foundEols.add(current);
lastEol = current;
}
}
}
// we mark space and WRAP_NEVER tokens as processed
for (FormatToken current = start; current != null && current != end;
current = current.next()) {
if (current.isVirtual()
&& !current.getKind().isIndentationMarker()
&& current.getKind() != FormatToken.Kind.BEFORE_JSX_BLOCK_START
&& current.getKind() != FormatToken.Kind.AFTER_JSX_BLOCK_END
&& getLineWrap(current, formatContext, codeStyle) != CodeStyle.WrapStyle.WRAP_IF_LONG) {
processed.add(current);
}
}
// FIXME if end is null we might be at EOF
if (end != null) {
// we fetch the space or next token to start
start = FormatTokenStream.getNextNonVirtual(start);
if (start == null) {
return;
}
// we must not cross the region boundaries in embedded case (might happen for broken source)
int regionEnd = formatContext.getEmbeddedRegionEnd(start.getOffset());
if (regionEnd >= 0) {
if (end.getOffset() > regionEnd) {
while (end != null && end != start && (end.isVirtual() || end.getOffset() > regionEnd)) {
end = end.previous();
}
}
if (lastEol != null && lastEol.getOffset() > regionEnd) {
while (lastEol != null && lastEol != start && (lastEol.isVirtual()
|| lastEol.getOffset() > regionEnd || lastEol.getKind() != FormatToken.Kind.EOL)) {
lastEol = lastEol.previous();
}
}
}
// end of cross the region fix
boolean remove = !isSpace(token, formatContext, codeStyle, true, false);
if (start.getKind() != FormatToken.Kind.WHITESPACE
&& start.getKind() != FormatToken.Kind.EOL) {
assert start == end : start + " " + end;
if (!remove) {
formatContext.insert(start.getOffset(), " "); // NOI18N
}
} else {
if (lastEol != null) {
end = lastEol;
}
if (foundEols != null) {
end = getEndingEol(token, codeStyle, foundEols, end);
}
// if it should be removed or there is eol (in fact space)
// which will stay there
if (remove || end.getKind() == FormatToken.Kind.EOL) {
if (start != end) {
formatContext.remove(start.getOffset(),
end.getOffset() - start.getOffset());
}
if (lastEol != null && start != end) {
moveForward(start, end, formatContext, codeStyle, false);
}
} else {
formatContext.replace(start.getOffset(),
end.getOffset() - start.getOffset(), " "); // NOI18N
}
}
}
}
private static boolean surroundingsContains(FormatToken token, Set<FormatToken.Kind> kinds) {
assert token.isVirtual();
FormatToken item = token;
while (item != null && item.isVirtual()) {
if (kinds.contains(item.getKind())) {
return true;
}
item = item.next();
}
item = token.previous();
while (item != null && item.isVirtual()) {
if (kinds.contains(item.getKind())) {
return true;
}
item = item.next();
}
return false;
}
private void formatComment(FormatToken comment, FormatContext formatContext, CodeStyle.Holder codeStyle, int indent) {
// this assumes the firts line is already indented by EOL logic
assert comment.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| comment.getKind() == FormatToken.Kind.DOC_COMMENT
|| comment.getKind() == FormatToken.Kind.LINE_COMMENT;
if (comment.getKind() == FormatToken.Kind.LINE_COMMENT) {
return;
}
String text = comment.getText().toString();
if (!text.contains("\n")) { // NOI18N
return;
}
// mootools packager see issue #
if (comment.getKind() == FormatToken.Kind.BLOCK_COMMENT
&& MOOTOOLS_COMMENT.matcher(text).matches()) { // NOI18N
return;
}
for (int i = 0; i < text.length(); i++) {
char single = text.charAt(i);
if (single == '\n') { // NOI18N
// following lines are + 1 indented
formatContext.indentLine(comment.getOffset() + i + 1,
indent + 1, Indentation.ALLOWED, codeStyle);
}
}
}
private void formatJsx(List<FormatToken> tokens, FormatContext formatContext, int index,
CodeStyle.Holder codeStyle, int initialIndent, int continuationIndent,
Stack<FormatContext.ContinuationBlock> continuations) {
// this assumes the first line is already indented by EOL logic
FormatToken jsx = tokens.get(index);
assert jsx.getId() == JsTokenId.JSX_TEXT;
String text = jsx.getText().toString();
for (int i = 0; i < text.length(); i++) {
char single = text.charAt(i);
Character next = text.length() > i + 1 ? text.charAt(i + 1) : null;
formatContext.updateJsxPath(single, next);
if (single == '\n') {
// if the line is exceeding right margin we wrap on previous position
FormatToken indentationEnd = wrapOnEol(tokens, formatContext, index - 1, codeStyle, initialIndent, continuationIndent, continuations, i);
FormatToken indentationEndNext = indentationEnd == null ? null : indentationEnd.next();
while (indentationEndNext != null && (indentationEndNext.isVirtual() || indentationEndNext.getOffset() < jsx.getOffset() + i)) {
if (indentationEndNext.getKind() == FormatToken.Kind.BEFORE_JSX_BLOCK_START) {
formatContext.updateJsxIndentation(indentationEndNext);
}
indentationEndNext = indentationEndNext.next();
}
int indent = computeJsxIndentation(formatContext, jsx.getOffset() + i + 1);
if (indent >= 0) {
formatContext.indentLine(jsx.getOffset() + i + 1, indent, Indentation.ALLOWED, codeStyle);
}
formatContext.setCurrentLineStart(jsx.getOffset() + i + 1 + indent);
}
}
}
private static int computeJsxIndentation(FormatContext formatContext, int offset) {
try {
Context context = formatContext.getContext();
int htmlIndent = context.lineIndent(context.lineStartOffset(offset
+ formatContext.getOffsetDiff()));
int indent = (htmlIndent - formatContext.getBaseJsxIndentation()) + formatContext.getJsxIndentation();
return indent;
} catch (BadLocationException ex) {
LOGGER.log(Level.INFO, null, ex);
}
return -1;
}
private void formatBrace(List<FormatToken> tokens, int index, FormatContext formatContext, CodeStyle.Holder codeStyle,
int initialIndent, int continuationIndent, Stack<FormatContext.ContinuationBlock> continuations, boolean tokenProcessed) {
FormatToken token = tokens.get(index);
if (token.next() != null && token.next().getId() == JsTokenId.BRACKET_LEFT_CURLY) {
openedBraces.add(token);
}
if (getBracePlacement(token, codeStyle) == CodeStyle.BracePlacement.NEW_LINE) {
formatLineWrap(tokens, index, formatContext, codeStyle, initialIndent,
continuationIndent, continuations);
} else if (getBracePlacement(token, codeStyle) == CodeStyle.BracePlacement.NEW_LINE_INDENTED) {
if (token.next() != null && token.next().getId() == JsTokenId.BRACKET_LEFT_CURLY) {
formatLineWrap(tokens, index, formatContext, codeStyle, initialIndent,
continuationIndent, continuations);
}
} else if (getBracePlacement(token, codeStyle) == CodeStyle.BracePlacement.SAME_LINE) {
boolean canFormatBrace = true;
FormatToken startToken = null;
for (int j = index - 1; j >= 0; j--) {
FormatToken ft = tokens.get(j);
if (ft.getKind() != FormatToken.Kind.WHITESPACE
&& ft.getKind() != FormatToken.Kind.EOL) {
if (ft.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| ft.getKind() == FormatToken.Kind.LINE_COMMENT) {
// comment between the right parenthesis and left brace
// prevents from formatting on the same line
canFormatBrace = false;
}
break;
}
startToken = ft;
}
FormatToken endToken = FormatTokenStream.getNextImportant(token);
if (canFormatBrace && startToken != null && endToken != null && endToken.getId() == JsTokenId.BRACKET_LEFT_CURLY) {
// set the character before the opening brace to the space or empty string according to the code style
String spaceBeforeBrace = isSpace(token, formatContext, codeStyle) ? " " : ""; // NOI18N
formatContext.replace(startToken.getOffset() - formatContext.getOffsetDiff() + lastOffsetDiff,
endToken.getOffset() - startToken.getOffset() + formatContext.getOffsetDiff() - lastOffsetDiff, spaceBeforeBrace);
} else if (canFormatBrace) {
formatSpace(tokens, index, formatContext, codeStyle);
}
} else if (!tokenProcessed) {
// code style is set to "preserve existing" and brace token hasn't been process yet
// format is as a space marker
formatSpace(tokens, index, formatContext, codeStyle);
}
}
private void formatKeywordAlignment(List<FormatToken> tokens, int index, FormatContext formatContext, CodeStyle.Holder codeStyle,
int initialIndent, int continuationIndent, Stack<FormatContext.ContinuationBlock> continuations) {
FormatToken token = tokens.get(index);
final Set<JsTokenId> keywordIds = EnumSet.of(
JsTokenId.KEYWORD_ELSE,
JsTokenId.KEYWORD_CATCH,
JsTokenId.KEYWORD_FINALLY,
JsTokenId.KEYWORD_WHILE);
if (isKeywordOnNewline(token, codeStyle)) {
formatLineWrap(tokens, index, formatContext, codeStyle, initialIndent,
continuationIndent, continuations);
} else {
boolean canReformat = true;
FormatToken startToken = null;
for (int j = index - 1; j >= 0; j--) {
FormatToken ft = tokens.get(j);
if (ft.getKind() != FormatToken.Kind.WHITESPACE
&& ft.getKind() != FormatToken.Kind.EOL) {
if (ft.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| ft.getKind() == FormatToken.Kind.LINE_COMMENT) {
// comment that prevents from formatting on the same line
canReformat = false;
}
break;
}
startToken = ft;
}
FormatToken endToken = FormatTokenStream.getNextImportant(token);
FormatToken lastBeforeStart = null;
if (startToken != null) {
lastBeforeStart = startToken.previous();
while (lastBeforeStart != null && lastBeforeStart.getKind().isIndentationMarker()) {
lastBeforeStart = lastBeforeStart.previous();
}
}
if (canReformat && startToken != null
&& lastBeforeStart != null && lastBeforeStart.getKind() == FormatToken.Kind.AFTER_END_BRACE
&& endToken != null && keywordIds.contains(endToken.getId())) {
// set the character before the keyword to the space or empty string according to the code style
String spaceBeforeBrace = isSpace(token, formatContext, codeStyle) ? " " : ""; // NOI18N
formatContext.replace(startToken.getOffset() - formatContext.getOffsetDiff() + lastOffsetDiff,
endToken.getOffset() - startToken.getOffset() + formatContext.getOffsetDiff() - lastOffsetDiff, spaceBeforeBrace);
} else if (canReformat && !(lastBeforeStart != null && lastBeforeStart.getKind() == FormatToken.Kind.AFTER_STATEMENT)) {
formatSpace(tokens, index, formatContext, codeStyle);
}
}
}
private boolean updateContinuationStart(FormatContext formatContext, FormatToken token,
Stack<FormatContext.ContinuationBlock> continuations, boolean continuation) {
boolean change = false;
FormatToken nextImportant = continuation ? FormatTokenStream.getNextImportant(token) : token;
if (nextImportant != null && nextImportant.getKind() == FormatToken.Kind.TEXT) {
if (JsTokenId.BRACKET_LEFT_CURLY == nextImportant.getId()) {
// if the pending continuation originates from
// class X
// extends Y {
// we do not want to increase the continuation on left brace
FormatToken previous = nextImportant.previous();
if (previous == null
|| previous.getKind() != FormatToken.Kind.BEFORE_CLASS_DECLARATION_BRACE
|| continuation) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.CURLY, true));
formatContext.incContinuationLevel();
formatContext.setPendingContinuation(false);
processed.add(nextImportant);
change = true;
}
} else if (JsTokenId.BRACKET_LEFT_BRACKET == nextImportant.getId()) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.BRACKET, true));
formatContext.incContinuationLevel();
formatContext.setPendingContinuation(false);
processed.add(nextImportant);
change = true;
} else if (JsTokenId.BRACKET_LEFT_PAREN == nextImportant.getId()) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.PAREN, true));
formatContext.incContinuationLevel();
formatContext.setPendingContinuation(false);
processed.add(nextImportant);
change = true;
} else if (JsTokenId.KEYWORD_FUNCTION == nextImportant.getId()) {
FormatToken curly = nextImportant;
while (curly != null) {
if (!curly.isVirtual()) {
if (JsTokenId.BRACKET_RIGHT_CURLY == curly.getId()) {
// safety catch - something wrong
curly = null;
break;
}
if (JsTokenId.BRACKET_LEFT_CURLY == curly.getId()) {
break;
}
}
curly = curly.next();
}
if (curly != null) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.CURLY, true));
formatContext.incContinuationLevel();
formatContext.setPendingContinuation(false);
processed.add(curly);
change = true;
}
} else if (continuation) {
formatContext.setPendingContinuation(true);
}
}
return change;
}
private void updateContinuationEnd(FormatContext formatContext, FormatToken token,
Stack<FormatContext.ContinuationBlock> continuations) {
if (token.isVirtual() || token.getKind() != FormatToken.Kind.TEXT) {
return;
}
if (formatContext.isPendingContinuation() && (JsTokenId.BRACKET_RIGHT_CURLY == token.getId()
|| JsTokenId.BRACKET_RIGHT_BRACKET == token.getId()
|| JsTokenId.BRACKET_RIGHT_PAREN == token.getId())) {
formatContext.setPendingContinuation(false);
}
// if the pending continuation originates from
// class X
// extends Y {
// we want to finish it on left before class declaration brace
if (formatContext.isPendingContinuation() && JsTokenId.BRACKET_LEFT_CURLY == token.getId()) {
FormatToken previous = token.previous();
if (previous != null && previous.getKind() == FormatToken.Kind.BEFORE_CLASS_DECLARATION_BRACE) {
formatContext.setPendingContinuation(false);
}
}
if (continuations.isEmpty()) {
return;
}
if (JsTokenId.BRACKET_LEFT_CURLY == token.getId()) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.CURLY, false));
} else if (JsTokenId.BRACKET_LEFT_BRACKET == token.getId()) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.BRACKET, false));
} else if (JsTokenId.BRACKET_LEFT_PAREN == token.getId()) {
continuations.push(new FormatContext.ContinuationBlock(
FormatContext.ContinuationBlock.Type.PAREN, false));
} else if (JsTokenId.BRACKET_RIGHT_CURLY == token.getId()) {
FormatContext.ContinuationBlock block = continuations.peek();
if (block.getType() == FormatContext.ContinuationBlock.Type.CURLY) {
continuations.pop();
if (block.isChange()) {
formatContext.decContinuationLevel();
}
}
} else if (JsTokenId.BRACKET_RIGHT_BRACKET == token.getId()) {
FormatContext.ContinuationBlock block = continuations.peek();
if (block.getType() == FormatContext.ContinuationBlock.Type.BRACKET) {
continuations.pop();
if (block.isChange()) {
formatContext.decContinuationLevel();
}
}
} else if (JsTokenId.BRACKET_RIGHT_PAREN == token.getId()) {
FormatContext.ContinuationBlock block = continuations.peek();
if (block.getType() == FormatContext.ContinuationBlock.Type.PAREN) {
continuations.pop();
if (block.isChange()) {
formatContext.decContinuationLevel();
}
}
}
}
private static boolean isContinuation(FormatContext formatContext,
CodeStyle.Holder codeStyle, FormatToken token, boolean noRealEol) {
assert noRealEol || token.getKind() == FormatToken.Kind.SOURCE_START
|| token.getKind() == FormatToken.Kind.EOL;
if (token.getKind() == FormatToken.Kind.SOURCE_START) {
return false;
}
FormatToken next = token.next();
if (JsTokenId.KEYWORD_IMPORT == next.getId()
|| JsTokenId.KEYWORD_EXPORT == next.getId()) {
return false;
}
for (FormatToken current = next; current != null && current.isVirtual(); current = current.next()) {
if (current.getKind() == FormatToken.Kind.AFTER_STATEMENT
|| current.getKind() == FormatToken.Kind.AFTER_PROPERTY
|| current.getKind() == FormatToken.Kind.AFTER_ELEMENT
|| current.getKind() == FormatToken.Kind.AFTER_DECORATOR
|| current.getKind() == FormatToken.Kind.AFTER_ARRAY_LITERAL_ITEM
|| current.getKind() == FormatToken.Kind.AFTER_CASE
// do not suppose continuation when indentation is changed
|| current.getKind().isIndentationMarker()) {
return false;
}
}
FormatToken nonVirtualNext = FormatTokenStream.getNextNonVirtual(next);
if (nonVirtualNext != null) {
// this may happen when curly bracket or array bracket is on new line
if (JsTokenId.BRACKET_LEFT_CURLY == nonVirtualNext.getId()
|| JsTokenId.BRACKET_LEFT_BRACKET == nonVirtualNext.getId()) {
FormatToken previous = nonVirtualNext.previous();
if (previous == null
|| (previous.getKind() != FormatToken.Kind.BEFORE_OBJECT
&& previous.getKind() != FormatToken.Kind.BEFORE_ARRAY)) {
return false;
} else if (previous.getKind() == FormatToken.Kind.BEFORE_OBJECT
|| previous.getKind() == FormatToken.Kind.BEFORE_ARRAY) {
// handles continuation before object literal / array (issues #227007 and #250150)
// find token before object/array, ignoring WS and EOL
FormatToken tokenBeforeObject = previous.previous();
while (tokenBeforeObject != null
&& (tokenBeforeObject.getKind() == FormatToken.Kind.WHITESPACE
|| tokenBeforeObject.getKind() == FormatToken.Kind.EOL)) {
tokenBeforeObject = tokenBeforeObject.previous();
}
// if we have an object literal / array as function argument
// or it's assigned to a variable, indent it according to the settings
if (tokenBeforeObject != null) {
switch (tokenBeforeObject.getKind()) {
case BEFORE_FUNCTION_CALL_ARGUMENT:
case AFTER_ASSIGNMENT_OPERATOR:
case AFTER_ASSIGNMENT_OPERATOR_WRAP:
case AFTER_WITH_PARENTHESIS:
case AFTER_TERNARY_OPERATOR:
return codeStyle.objectLiteralContinuation;
case AFTER_PROPERTY_OPERATOR:
FormatToken operatorToken = tokenBeforeObject.previous();
if (operatorToken != null && operatorToken.getId() == JsTokenId.OPERATOR_COLON) {
return codeStyle.objectLiteralContinuation;
}
}
}
}
} else if (JsTokenId.BRACKET_RIGHT_CURLY == nonVirtualNext.getId()
|| JsTokenId.BRACKET_RIGHT_BRACKET == nonVirtualNext.getId()
|| JsTokenId.KEYWORD_IMPORT == nonVirtualNext.getId()
|| JsTokenId.KEYWORD_EXPORT == nonVirtualNext.getId()) {
return false;
// this may happen when comma separating object memebers is on new line
} else if (JsTokenId.OPERATOR_COMMA == nonVirtualNext.getId()) {
FormatToken virtualNext = nonVirtualNext.next();
while (virtualNext != null && virtualNext.isVirtual()) {
if (virtualNext.getKind() == FormatToken.Kind.AFTER_PROPERTY) {
return false;
}
virtualNext = virtualNext.next();
}
}
}
// search backwards for important token
FormatToken result = null;
for (FormatToken previous = noRealEol ? token : token.previous(); previous != null;
previous = previous.previous()) {
FormatToken.Kind kind = previous.getKind();
if (kind == FormatToken.Kind.SOURCE_START
|| kind == FormatToken.Kind.TEXT
|| kind == FormatToken.Kind.AFTER_STATEMENT
|| kind == FormatToken.Kind.AFTER_PROPERTY
|| kind == FormatToken.Kind.AFTER_ELEMENT
|| kind == FormatToken.Kind.AFTER_DECORATOR
|| kind == FormatToken.Kind.AFTER_ARRAY_LITERAL_ITEM
|| kind == FormatToken.Kind.AFTER_CASE
// do not suppose continuation when indentation is changed
|| kind.isIndentationMarker()) {
result = previous;
break;
}
}
if (result == null
|| result.getKind() == FormatToken.Kind.SOURCE_START
|| result.getKind() == FormatToken.Kind.AFTER_STATEMENT
|| result.getKind() == FormatToken.Kind.AFTER_ELEMENT
|| result.getKind() == FormatToken.Kind.AFTER_DECORATOR
|| result.getKind() == FormatToken.Kind.AFTER_ARRAY_LITERAL_ITEM
|| result.getKind() == FormatToken.Kind.AFTER_CASE
// do not suppose continuation when indentation is changed
|| result.getKind().isIndentationMarker()) {
return false;
} else if (result.getKind() == FormatToken.Kind.AFTER_PROPERTY) {
FormatToken nextNonWs = next;
while (nextNonWs != null
&& (nextNonWs.getKind() == FormatToken.Kind.WHITESPACE
|| nextNonWs.getKind() == FormatToken.Kind.EOL)) {
nextNonWs = nextNonWs.next();
}
return nextNonWs != null
&& nextNonWs.getKind() == FormatToken.Kind.BEFORE_FUNCTION_CALL_ARGUMENT;
}
return !(JsTokenId.BRACKET_LEFT_CURLY == result.getId()
|| JsTokenId.BRACKET_RIGHT_CURLY == result.getId()
|| (formatContext.isBrokenSource() && JsTokenId.OPERATOR_SEMICOLON == result.getId())
|| formatContext.isGenerated(result));
}
// FIXME can we movet his to FormatContext ?
private Indentation checkIndentation(Document doc, FormatToken token, FormatToken indentationEnd,
FormatContext formatContext, Context context, int indentationSize) {
assert indentationEnd != null && !indentationEnd.isVirtual() : indentationEnd;
assert token.getKind() == FormatToken.Kind.EOL || token.getKind() == FormatToken.Kind.SOURCE_START;
// this: (token.getKind() != FormatToken.Kind.SOURCE_START
// && formatContext.getDocumentOffset(token.getOffset()) >= 0)
// handles the case when virtual source for embedded code contains
// non existing eols we must not do indentation on these
if ((token.getKind() != FormatToken.Kind.SOURCE_START && formatContext.getDocumentOffset(token.getOffset()) >= 0)
|| (context.startOffset() <= 0 && !formatContext.isEmbedded())) {
// we don't want to touch lines starting with other language
// it is a bit heuristic but we can't do much
// see embeddedMultipleSections1.php
if (formatContext.isGenerated(indentationEnd)) {
return Indentation.FORBIDDEN;
}
return Indentation.ALLOWED;
}
// we are sure this is SOURCE_START - no source start indentation in embedded code
if (formatContext.isEmbedded()) {
return Indentation.FORBIDDEN;
}
try {
// when we are formatting only selection we
// have to handle the source start indentation properly
int lineStartOffset = IndentUtils.lineStartOffset(doc, context.startOffset());
if (isWhitespace(doc.getText(lineStartOffset, context.startOffset() - lineStartOffset))) {
int currentIndentation = IndentUtils.lineIndent(doc, lineStartOffset);
if (currentIndentation != indentationSize) {
// fix the indentation if possible
if (lineStartOffset + indentationSize >= context.startOffset()) {
return new Indentation(true, true);
}
}
}
} catch (BadLocationException ex) {
LOGGER.log(Level.INFO, null, ex);
}
return Indentation.FORBIDDEN;
}
private static void updateIndentationLevel(FormatToken token,
FormatContext formatContext, CodeStyle.Holder codeStyle) {
switch (token.getKind()) {
case ELSE_IF_INDENTATION_INC:
if (ELSE_IF_SINGLE_LINE) {
break;
}
case INDENTATION_INC:
formatContext.incIndentationLevel();
break;
case ELSE_IF_INDENTATION_DEC:
if (ELSE_IF_SINGLE_LINE) {
break;
}
case INDENTATION_DEC:
formatContext.decIndentationLevel();
break;
case BEFORE_JSX_BLOCK_START:
formatContext.incJsxIndentation(token);
break;
case AFTER_JSX_BLOCK_END:
formatContext.decJsxIndentation(token);
break;
default:
break;
}
// following code handles indentation of the opening brace
// if indentation is set to "new line indented"
if (token.getKind().isBraceMarker()) {
if (getBracePlacement(token, codeStyle) == CodeStyle.BracePlacement.NEW_LINE_INDENTED) {
FormatToken nextFt = token.next();
if (nextFt != null && nextFt.getId() == JsTokenId.BRACKET_LEFT_CURLY) {
// indent only in case if the token after brace marker is left brace
formatContext.incIndentationLevel();
}
}
}
}
// contains all eols in case of multiple empty lines
// including tokenAfterEol
private static List<FormatToken> getExtendedTokenAfterEol(FormatToken tokenAfterEol) {
List<FormatToken> eols = new ArrayList<>(2);
eols.add(tokenAfterEol);
for (FormatToken current = tokenAfterEol; current != null && (current.getKind() == FormatToken.Kind.EOL
|| current.getKind() == FormatToken.Kind.WHITESPACE
|| current.isVirtual()); current = current.next()) {
if (current != tokenAfterEol && current.getKind() == FormatToken.Kind.EOL) {
eols.add(current);
}
}
return eols;
}
private static boolean isStatementWrap(FormatToken token) {
return token.getKind() == FormatToken.Kind.AFTER_STATEMENT
|| token.getKind() == FormatToken.Kind.AFTER_BLOCK_START
|| token.getKind() == FormatToken.Kind.AFTER_CASE
|| token.getKind() == FormatToken.Kind.ELSE_IF_AFTER_BLOCK_START;
}
private static CodeStyle.WrapStyle getLineWrap(List<FormatToken> tokens, int index,
FormatContext context, CodeStyle.Holder codeStyle, boolean skipWitespace) {
FormatToken token = tokens.get(index);
assert token.isVirtual();
FormatToken next = token;
while (next != null && (next.isVirtual() || skipWitespace && next.getKind() == FormatToken.Kind.WHITESPACE)) {
CodeStyle.WrapStyle style = getLineWrap(next, context, codeStyle);
if (style != null) {
return style;
}
next = next.next();
}
return null;
}
private static FormatToken getEndingEol(FormatToken token, CodeStyle.Holder codeStyle, List<FormatToken> eols, FormatToken defaultEnd) {
FormatToken end = defaultEnd;
if (codeStyle.maxPreservedObjectLines > 0
&& surroundingsContains(token, EnumSet.of(
FormatToken.Kind.AFTER_OBJECT_START, FormatToken.Kind.AFTER_PROPERTY))) {
if (codeStyle.maxPreservedObjectLines < (eols.size() - 1)) {
end = eols.get(eols.size() - codeStyle.maxPreservedObjectLines - 1);
} else {
end = eols.get(0);
}
}
if (codeStyle.maxPreservedArrayLines > 0
&& surroundingsContains(token, EnumSet.of(
FormatToken.Kind.AFTER_ARRAY_LITERAL_START, FormatToken.Kind.AFTER_ARRAY_LITERAL_ITEM))) {
if (codeStyle.maxPreservedArrayLines < (eols.size() - 1)) {
end = eols.get(eols.size() - codeStyle.maxPreservedArrayLines - 1);
} else {
end = eols.get(0);
}
}
if (codeStyle.maxPreservedClassLines > 0
&& surroundingsContains(token, EnumSet.of(
FormatToken.Kind.AFTER_CLASS_START, FormatToken.Kind.AFTER_ELEMENT))) {
if (codeStyle.maxPreservedClassLines < (eols.size() - 1)) {
end = eols.get(eols.size() - codeStyle.maxPreservedClassLines - 1);
} else {
end = eols.get(0);
}
}
return end;
}
private static CodeStyle.WrapStyle getLineWrap(FormatToken token, FormatContext context, CodeStyle.Holder codeStyle) {
switch (token.getKind()) {
case AFTER_STATEMENT:
// this is special because of possible an typical "something."
// dot sanitization see #234385
FormatToken check = token.previous();
if (check != null && check.getKind() == FormatToken.Kind.BEFORE_DOT) {
return null;
}
check = token.next();
if (check != null && check.getKind() == FormatToken.Kind.BEFORE_DOT) {
return null;
}
return codeStyle.wrapStatement;
case AFTER_BLOCK_START:
// XXX option
if (isEmptyFunctionBlock(token)) {
return CodeStyle.WrapStyle.WRAP_NEVER;
}
return CodeStyle.WrapStyle.WRAP_ALWAYS;
case AFTER_CASE:
// XXX option
return CodeStyle.WrapStyle.WRAP_ALWAYS;
case ELSE_IF_AFTER_BLOCK_START:
if (ELSE_IF_SINGLE_LINE) {
return CodeStyle.WrapStyle.WRAP_NEVER;
}
// XXX option
return CodeStyle.WrapStyle.WRAP_ALWAYS;
case AFTER_VAR_DECLARATION:
return codeStyle.wrapVariables;
case BEFORE_FUNCTION_DECLARATION_PARAMETER:
return codeStyle.wrapMethodParams;
case BEFORE_FUNCTION_CALL_ARGUMENT:
return codeStyle.wrapMethodCallArgs;
case AFTER_IF_START:
return codeStyle.wrapIfStatement;
case AFTER_ELSE_START:
return codeStyle.wrapIfStatement;
case AFTER_WHILE_START:
return codeStyle.wrapWhileStatement;
case AFTER_DO_START:
return codeStyle.wrapDoWhileStatement;
case AFTER_FOR_START:
return codeStyle.wrapForStatement;
case AFTER_WITH_START:
return codeStyle.wrapWithStatement;
case BEFORE_FOR_TEST:
case BEFORE_FOR_MODIFY:
return codeStyle.wrapFor;
case BEFORE_CHAIN_CALL_DOT:
if (codeStyle.wrapAfterDotInChainedMethodCalls) {
return null;
}
return codeStyle.wrapChainedMethodCalls;
case AFTER_CHAIN_CALL_DOT:
if (codeStyle.wrapAfterDotInChainedMethodCalls) {
return codeStyle.wrapChainedMethodCalls;
}
return null;
case AFTER_BINARY_OPERATOR_WRAP:
if (codeStyle.wrapAfterBinaryOps) {
return codeStyle.wrapBinaryOps;
}
return null;
case BEFORE_BINARY_OPERATOR_WRAP:
if (codeStyle.wrapAfterBinaryOps) {
return null;
}
return codeStyle.wrapBinaryOps;
case AFTER_ASSIGNMENT_OPERATOR_WRAP:
return codeStyle.wrapAssignOps;
case AFTER_ARROW_OPERATOR_WRAP:
return codeStyle.wrapArrowOps;
case AFTER_TERNARY_OPERATOR_WRAP:
if (codeStyle.wrapAfterTernaryOps) {
return codeStyle.wrapTernaryOps;
}
return null;
case BEFORE_TERNARY_OPERATOR_WRAP:
if (codeStyle.wrapAfterTernaryOps) {
return null;
}
return codeStyle.wrapTernaryOps;
case AFTER_OBJECT_START:
case BEFORE_OBJECT_END:
// this is special because people usually don't want to wrap
// empty objects see #228716
// add an option wrap empty objects if anybody will complain
if (isEmptyObject(token)) {
return null;
}
return codeStyle.wrapObjects;
case AFTER_CLASS_START:
case BEFORE_CLASS_END:
return codeStyle.wrapClasses;
case BEFORE_CLASS_EXTENDS:
return codeStyle.wrapClassExtends;
case AFTER_PROPERTY:
return codeStyle.wrapProperties;
case AFTER_ELEMENT:
return codeStyle.wrapElements;
case AFTER_DECORATOR:
return codeStyle.wrapDecorators;
case AFTER_ARRAY_LITERAL_START:
case BEFORE_ARRAY_LITERAL_END:
return codeStyle.wrapArrayInit;
case AFTER_ARRAY_LITERAL_ITEM:
return codeStyle.wrapArrayInitItems;
default:
return null;
}
}
private static CodeStyle.BracePlacement getBracePlacement(FormatToken token, CodeStyle.Holder codeStyle) {
switch (token.getKind()) {
case BEFORE_FUNCTION_DECLARATION_BRACE:
return codeStyle.functionDeclBracePlacement;
case BEFORE_CLASS_DECLARATION_BRACE:
return codeStyle.classDeclBracePlacement;
case BEFORE_IF_BRACE:
case BEFORE_ELSE_BRACE:
return codeStyle.ifBracePlacement;
case BEFORE_WHILE_BRACE:
case BEFORE_DO_BRACE:
return codeStyle.whileBracePlacement;
case BEFORE_FOR_BRACE:
return codeStyle.forBracePlacement;
case BEFORE_SWITCH_BRACE:
return codeStyle.switchBracePlacement;
case BEFORE_TRY_BRACE:
case BEFORE_CATCH_BRACE:
case BEFORE_FINALLY_BRACE:
return codeStyle.catchBracePlacement;
case BEFORE_WITH_BRACE:
return codeStyle.withBracePlacement;
default:
return CodeStyle.BracePlacement.PRESERVE_EXISTING;
}
}
private static boolean isKeywordOnNewline(FormatToken token, CodeStyle.Holder codeStyle) {
switch (token.getKind()) {
case BEFORE_ELSE_KEYWORD:
return codeStyle.placeElseOnNewLine;
case BEFORE_WHILE_KEYWORD:
return codeStyle.placeWhileOnNewLine;
case BEFORE_CATCH_KEYWORD:
return codeStyle.placeCatchOnNewLine;
case BEFORE_FINALLY_KEYWORD:
return codeStyle.placeFinallyOnNewLine;
default:
return false;
}
}
private static boolean isSpace(FormatToken token, FormatContext context, CodeStyle.Holder codeStyle,
boolean skipWitespace, boolean skipEol) {
if (!(token.isVirtual()
|| skipWitespace && token.getKind() == FormatToken.Kind.WHITESPACE
|| skipEol && token.getKind() == FormatToken.Kind.EOL)) {
return false;
}
boolean hasSpaceMarker = false;
boolean hasSpace = false;
FormatToken next = token;
while (next != null && (next.isVirtual()
|| skipWitespace && next.getKind() == FormatToken.Kind.WHITESPACE
|| skipWitespace && next.getKind() == FormatToken.Kind.EOL)) {
if (next.getKind() != FormatToken.Kind.WHITESPACE
&& next.getKind() != FormatToken.Kind.EOL) {
if (isSpace(next, context, codeStyle)) {
return true;
}
if (next.getKind().isSpaceMarker()) {
hasSpaceMarker = true;
}
} else {
hasSpace = true;
}
next = next.next();
}
return !hasSpaceMarker && hasSpace;
}
private static boolean isSpace(FormatToken token, FormatContext formatContext, CodeStyle.Holder codeStyle) {
switch (token.getKind()) {
case BEFORE_ASSIGNMENT_OPERATOR:
return codeStyle.spaceAroundAssignOps;
case AFTER_ASSIGNMENT_OPERATOR:
return codeStyle.spaceAroundAssignOps;
case BEFORE_ARROW_OPERATOR:
return codeStyle.spaceAroundArrowOps;
case AFTER_ARROW_OPERATOR:
return codeStyle.spaceAroundArrowOps;
case BEFORE_PROPERTY_OPERATOR:
return codeStyle.spaceBeforeColon;
case AFTER_PROPERTY_OPERATOR:
return codeStyle.spaceAfterColon;
case BEFORE_BINARY_OPERATOR:
return codeStyle.spaceAroundBinaryOps;
case AFTER_BINARY_OPERATOR:
return codeStyle.spaceAroundBinaryOps;
case BEFORE_COMMA:
return codeStyle.spaceBeforeComma;
case AFTER_COMMA:
return codeStyle.spaceAfterComma;
case AFTER_IF_KEYWORD:
return codeStyle.spaceBeforeIfParen;
case AFTER_WHILE_KEYWORD:
return codeStyle.spaceBeforeWhileParen;
case AFTER_FOR_KEYWORD:
return codeStyle.spaceBeforeForParen;
case AFTER_WITH_KEYWORD:
return codeStyle.spaceBeforeWithParen;
case AFTER_SWITCH_KEYWORD:
return codeStyle.spaceBeforeSwitchParen;
case AFTER_CATCH_KEYWORD:
return codeStyle.spaceBeforeCatchParen;
case BEFORE_WHILE_KEYWORD:
return codeStyle.spaceBeforeWhile;
case BEFORE_ELSE_KEYWORD:
return codeStyle.spaceBeforeElse;
case BEFORE_CATCH_KEYWORD:
return codeStyle.spaceBeforeCatch;
case BEFORE_FINALLY_KEYWORD:
return codeStyle.spaceBeforeFinally;
case BEFORE_SEMICOLON:
return codeStyle.spaceBeforeSemi;
case AFTER_SEMICOLON:
return codeStyle.spaceAfterSemi;
case BEFORE_UNARY_OPERATOR:
return codeStyle.spaceAroundUnaryOps;
case AFTER_UNARY_OPERATOR:
return codeStyle.spaceAroundUnaryOps;
case BEFORE_TERNARY_OPERATOR:
return codeStyle.spaceAroundTernaryOps;
case AFTER_TERNARY_OPERATOR:
return codeStyle.spaceAroundTernaryOps;
case BEFORE_ANONYMOUS_FUNCTION_DECLARATION:
return codeStyle.spaceBeforeAnonMethodDeclParen;
case BEFORE_FUNCTION_DECLARATION:
return codeStyle.spaceBeforeMethodDeclParen;
case BEFORE_FUNCTION_CALL:
return codeStyle.spaceBeforeMethodCallParen;
case AFTER_FUNCTION_DECLARATION_PARENTHESIS:
return codeStyle.spaceWithinMethodDeclParens;
case BEFORE_FUNCTION_DECLARATION_PARENTHESIS:
return codeStyle.spaceWithinMethodDeclParens;
case AFTER_FUNCTION_CALL_PARENTHESIS:
return codeStyle.spaceWithinMethodCallParens;
case BEFORE_FUNCTION_CALL_PARENTHESIS:
return codeStyle.spaceWithinMethodCallParens;
case AFTER_IF_PARENTHESIS:
return codeStyle.spaceWithinIfParens;
case BEFORE_IF_PARENTHESIS:
return codeStyle.spaceWithinIfParens;
case AFTER_WHILE_PARENTHESIS:
return codeStyle.spaceWithinWhileParens;
case BEFORE_WHILE_PARENTHESIS:
return codeStyle.spaceWithinWhileParens;
case AFTER_FOR_PARENTHESIS:
return codeStyle.spaceWithinForParens;
case BEFORE_FOR_PARENTHESIS:
return codeStyle.spaceWithinForParens;
case AFTER_WITH_PARENTHESIS:
return codeStyle.spaceWithinWithParens;
case BEFORE_WITH_PARENTHESIS:
return codeStyle.spaceWithinWithParens;
case AFTER_SWITCH_PARENTHESIS:
return codeStyle.spaceWithinSwitchParens;
case BEFORE_SWITCH_PARENTHESIS:
return codeStyle.spaceWithinSwitchParens;
case AFTER_CATCH_PARENTHESIS:
return codeStyle.spaceWithinCatchParens;
case BEFORE_CATCH_PARENTHESIS:
return codeStyle.spaceWithinCatchParens;
case AFTER_LEFT_PARENTHESIS:
return codeStyle.spaceWithinParens;
case BEFORE_RIGHT_PARENTHESIS:
return codeStyle.spaceWithinParens;
case AFTER_LEFT_BRACE:
return codeStyle.spaceWithinBraces;
case BEFORE_RIGHT_BRACE:
return codeStyle.spaceWithinBraces;
case BEFORE_IF_BRACE:
return codeStyle.spaceBeforeIfLeftBrace;
case BEFORE_ELSE_BRACE:
return codeStyle.spaceBeforeElseLeftBrace;
case BEFORE_WHILE_BRACE:
return codeStyle.spaceBeforeWhileLeftBrace;
case BEFORE_FOR_BRACE:
return codeStyle.spaceBeforeForLeftBrace;
case BEFORE_DO_BRACE:
return codeStyle.spaceBeforeDoLeftBrace;
case BEFORE_TRY_BRACE:
return codeStyle.spaceBeforeTryLeftBrace;
case BEFORE_CATCH_BRACE:
return codeStyle.spaceBeforeCatchLeftBrace;
case BEFORE_FINALLY_BRACE:
return codeStyle.spaceBeforeFinallyLeftBrace;
case BEFORE_SWITCH_BRACE:
return codeStyle.spaceBeforeSwitchLeftBrace;
case BEFORE_WITH_BRACE:
return codeStyle.spaceBeforeWithLeftBrace;
case BEFORE_FUNCTION_DECLARATION_BRACE:
return codeStyle.spaceBeforeMethodDeclLeftBrace;
case BEFORE_CLASS_DECLARATION_BRACE:
return codeStyle.spaceBeforeClassDeclLeftBrace;
case AFTER_ARRAY_LITERAL_BRACKET:
return codeStyle.spaceWithinArrayBrackets;
case BEFORE_ARRAY_LITERAL_BRACKET:
return codeStyle.spaceWithinArrayBrackets;
case AFTER_NEW_KEYWORD:
// no option as false (removing space) would brake the code
return true;
case AFTER_VAR_KEYWORD:
// no option as false (removing space) would brake the code
return true;
case AFTER_TYPEOF_KEYWORD:
// no option as false (removing space) would brake the code
return true;
case BEFORE_DOT:
case AFTER_DOT:
return false;
default:
return false;
}
}
private static boolean isEmptyObject(FormatToken token) {
if (token.getKind() == FormatToken.Kind.AFTER_OBJECT_START) {
FormatToken current = token.next();
while (current != null && current.isVirtual()) {
if (current.getKind() == FormatToken.Kind.BEFORE_OBJECT_END) {
return true;
}
current = current.next();
}
return false;
} else if (token.getKind() == FormatToken.Kind.BEFORE_OBJECT_END) {
FormatToken current = token.previous();
while (current != null && current.isVirtual()) {
if (current.getKind() == FormatToken.Kind.AFTER_OBJECT_START) {
return true;
}
current = current.previous();
}
return false;
}
return false;
}
private static boolean isEmptyFunctionBlock(FormatToken token) {
if (token.getKind() == FormatToken.Kind.AFTER_BLOCK_START) {
// now look back to find out what kind of block we are in
FormatToken prev = token.previous();
while (prev != null && (!prev.isVirtual() || prev.getKind().isIndentationMarker())) {
prev = prev.previous();
}
if (prev != null && prev.getKind() == FormatToken.Kind.BEFORE_FUNCTION_DECLARATION_BRACE) {
// continue only in case of function
FormatToken current = token.next();
while (current != null && (current.isVirtual() || current.getKind() == FormatToken.Kind.WHITESPACE)) {
current = current.next();
}
return current != null && current.getId() == JsTokenId.BRACKET_RIGHT_CURLY;
}
}
return false;
}
private int getFormatStableStart(Document doc, Language<JsTokenId> language,
int offset, int startOffset, boolean embedded) {
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
TokenHierarchy.get(doc), offset, language);
if (ts == null) {
return 0;
}
ts.move(startOffset);
if (!ts.movePrevious()) {
return 0;
}
// Look backwards to find a suitable context
// which we will assume is properly indented and balanced
int curlyBalance = 0;
do {
Token<?extends JsTokenId> token = ts.token();
JsTokenId id = token.id();
// FIXME should we check for more tokens like {, if, else ...
switch (id) {
case KEYWORD_FUNCTION:
if (curlyBalance > 0 && ts.offset() < offset) {
return ts.offset();
}
break;
case BRACKET_LEFT_CURLY:
curlyBalance++;
break;
case BRACKET_RIGHT_CURLY:
curlyBalance--;
break;
default:
break;
}
} while (ts.movePrevious());
if (embedded && !ts.movePrevious()) {
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld != null) {
// I may have moved to the front of an embedded JavaScript area, e.g. in
// an attribute or in a <script> tag. If this is the end of the line,
// go to the next line instead since the reindent code will go to the beginning
// of the stable formatting start.
int sequenceBegin = ts.offset();
try {
int lineTextEnd = LineDocumentUtils.getLineLastNonWhitespace(ld, sequenceBegin);
if (lineTextEnd == -1 || sequenceBegin > lineTextEnd) {
return Math.min(doc.getLength(), LineDocumentUtils.getLineEnd(ld, sequenceBegin) + 1);
}
} catch (BadLocationException ex) {
LOGGER.log(Level.INFO, null, ex);
}
}
}
return ts.offset();
}
private boolean isContinuation(Document doc, int offset, int bracketBalance,
boolean continued, int bracketBalanceDelta, IndentContext.BlockDescription block) throws BadLocationException {
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld == null) {
return false;
}
offset = LineDocumentUtils.getLineLastNonWhitespace(ld, offset);
if (offset == -1) {
return false;
}
TokenSequence<? extends JsTokenId> ts = LexUtilities.getPositionedSequence(doc, offset, language);
Token<? extends JsTokenId> token = (ts != null ? ts.token() : null);
if (ts != null && token != null) {
int index = ts.index();
JsTokenId previousId = null;
if (ts.movePrevious()) {
Token<? extends JsTokenId> previous = LexUtilities.findPreviousNonWsNonComment(ts);
if (previous != null) {
previousId = previous.id();
}
ts.moveIndex(index);
ts.moveNext();
}
JsTokenId id = token.id();
boolean isContinuationOperator = LexUtilities.isBinaryOperator(id, previousId);
if (ts.offset() == offset && token.length() > 1 && token.text().toString().startsWith("\\")) {
// Continued lines have different token types
isContinuationOperator = true;
}
if (id == JsTokenId.OPERATOR_COMMA) {
// If there's a comma it's a continuation operator, but inside arrays, hashes or parentheses
// parameter lists we should not treat it as such since we'd "double indent" the items, and
// NOT the first item (where there's no comma, e.g. you'd have
// foo(
// firstarg,
// secondarg, # indented both by ( and hanging indent ,
// thirdarg)
// same if there is a comma in object for example
// var a = {
// b : "something", # should not be indented as continuation
// c : "else"
// }
isContinuationOperator = (bracketBalance == 0) && (block == null || !block.isObject());
} else if (id == JsTokenId.BRACKET_LEFT_PAREN) {
isContinuationOperator = true;
} else if (id == JsTokenId.BRACKET_LEFT_CURLY) {
isContinuationOperator = (bracketBalanceDelta >= 0) && continued;
} else if (id == JsTokenId.OPERATOR_COLON) {
TokenSequence<? extends JsTokenId> inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
Token<? extends JsTokenId> foundToken = LexUtilities.findPreviousIncluding(inner,
Arrays.asList(JsTokenId.KEYWORD_CASE, JsTokenId.KEYWORD_DEFAULT, JsTokenId.OPERATOR_COLON));
if (foundToken != null && (foundToken.id() == JsTokenId.KEYWORD_CASE
|| foundToken.id() == JsTokenId.KEYWORD_DEFAULT)) {
isContinuationOperator = false;
} else {
isContinuationOperator = true;
}
} else {
JsTokenId nextId = null;
if (ts.moveNext()) {
Token<? extends JsTokenId> next = LexUtilities.findNextNonWsNonComment(ts);
if (next != null) {
nextId = next.id();
}
ts.moveIndex(index);
ts.moveNext();
}
if (nextId == JsTokenId.BRACKET_RIGHT_PAREN) {
isContinuationOperator = true;
}
}
return isContinuationOperator;
}
return false;
}
// FIXME we do not reindent multiple regions
@Override
public void reindent(final Context context) {
Document document = context.document();
int startOffset = context.startOffset();
int endOffset = context.endOffset();
final IndentContext indentContext = new IndentContext(context, provider);
int indentationSize = IndentUtils.indentLevelSize(document);
int continuationIndent = CodeStyle.get(indentContext).getContinuationIndentSize();
try {
final Document doc = document; // document.getText(0, document.getLength())
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld == null) {
return;
}
startOffset = LineDocumentUtils.getLineStart(ld, startOffset);
int endLineOffset = LineDocumentUtils.getLineStart(ld, endOffset);
final boolean indentOnly = (startOffset == endLineOffset)
&& (endOffset == context.caretOffset() || startOffset == context.caretOffset())
&& (LineDocumentUtils.isLineEmpty(ld, startOffset)
|| LineDocumentUtils.isLineWhitespace(ld, startOffset)
|| LineDocumentUtils.getNextNonWhitespace(ld, startOffset) == context.caretOffset());
if (indentOnly && indentContext.isEmbedded()) {
// Make sure we're not messing with indentation in HTML
Token<? extends JsTokenId> token = LexUtilities.getToken(doc, startOffset, language);
if (token == null) {
return;
}
}
if (endOffset > doc.getLength()) {
endOffset = doc.getLength();
}
final int lineStart = startOffset;//Utilities.getRowStart(doc, startOffset);
int initialOffset = 0;
int initialIndent = 0;
if (startOffset > 0) {
int prevOffset = LineDocumentUtils.getLineStart(ld, startOffset-1);
initialOffset = getFormatStableStart(doc, language, prevOffset, startOffset, indentContext.isEmbedded());
initialIndent = GsfUtilities.getLineIndent(doc, initialOffset);
}
// Build up a set of offsets and indents for lines where I know I need
// to adjust the offset. I will then go back over the document and adjust
// lines that are different from the intended indent. By doing piecemeal
// replacements in the document rather than replacing the whole thing,
// a lot of things will work better: breakpoints and other line annotations
// will be left in place, semantic coloring info will not be temporarily
// damaged, and the caret will stay roughly where it belongs.
// When we're formatting sections, include whitespace on empty lines; this
// is used during live code template insertions for example. However, when
// wholesale formatting a whole document, leave these lines alone.
boolean indentEmptyLines = (startOffset != 0 || endOffset + 1 != doc.getLength());
boolean includeEnd = endOffset == doc.getLength() || indentOnly;
// TODO - remove initialbalance etc.
computeIndents(indentContext, initialIndent, indentationSize, continuationIndent, initialOffset, endOffset,
indentEmptyLines, includeEnd, indentOnly);
AtomicLockDocument ald = LineDocumentUtils.asRequired(doc, AtomicLockDocument.class);
ald.runAtomic(() -> {
try {
List<IndentContext.Indentation> indents = indentContext.getIndentations();
// Iterate in reverse order such that offsets are not affected by our edits
for (int i = indents.size() - 1; i >= 0; i--) {
IndentContext.Indentation indentation = indents.get(i);
int indent = indentation.getSize();
int lineBegin = indentation.getOffset();
if (lineBegin < lineStart) {
// We're now outside the region that the user wanted reformatting;
// these offsets were computed to get the correct continuation context etc.
// for the formatter
break;
}
if (lineBegin == lineStart && i > 0) {
// Look at the previous line, and see how it's indented
// in the buffer. If it differs from the computed position,
// offset my computed position (thus, I'm only going to adjust
// the new line position relative to the existing editing.
// This avoids the situation where you're inserting a newline
// in the middle of "incorrectly" indented code (e.g. different
// size than the IDE is using) and the newline position ending
// up "out of sync"
IndentContext.Indentation prevIndentation = indents.get(i - 1);
int prevOffset = prevIndentation.getOffset();
int prevIndent = prevIndentation.getSize();
int actualPrevIndent = GsfUtilities.getLineIndent(doc, prevOffset);
// NOTE: in embedding this is usually true as we have some nonzero initial indent,
// I am just not sure if it is better to add indentOnly check (as I did) or
// remove blank lines condition completely?
if (actualPrevIndent != prevIndent) {
// For blank lines, indentation may be 0, so don't adjust in that case
if (indentOnly || !(LineDocumentUtils.isLineEmpty(ld, prevOffset) || LineDocumentUtils.isLineWhitespace(ld, prevOffset))) {
indent = actualPrevIndent + (indent-prevIndent);
}
}
}
// Adjust the indent at the given line (specified by offset) to the given indent
int currentIndent = GsfUtilities.getLineIndent(doc, lineBegin);
if (currentIndent != indent && indent >= 0) {
//org.netbeans.editor.Formatter editorFormatter = doc.getFormatter();
//editorFormatter.changeRowIndent(doc, lineBegin, indent);
context.modifyIndent(lineBegin, indent);
}
}
} catch (BadLocationException ble) {
Exceptions.printStackTrace(ble);
}
});
} catch (BadLocationException ble) {
LOGGER.log(Level.FINE, null, ble);
}
}
private void computeIndents(IndentContext context, int initialIndent, int indentSize, int continuationIndent,
int startOffset, int endOffset, boolean indentEmptyLines, boolean includeEnd, boolean indentOnly) {
Document doc = context.getDocument();
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld == null) {
return;
}
// PENDING:
// The reformatting APIs in NetBeans should be lexer based. They are still
// based on the old TokenID apis. Once we get a lexer version, convert this over.
// I just need -something- in place until that is provided.
try {
// Algorithm:
// Iterate over the range.
// Accumulate a token balance ( {,(,[, and keywords like class, case, etc. increases the balance,
// },),] and "end" decreases it
// If the line starts with an end marker, indent the line to the level AFTER the token
// else indent the line to the level BEFORE the token (the level being the balance * indentationSize)
// Compute the initial balance and indentation level and use that as a "base".
// If the previous line is not "done" (ends with a comma or a binary operator like "+" etc.
// add a "hanging indent" modifier.
// At the end of the day, we're recording a set of line offsets and indents.
// This can be used either to reformat the buffer, or indent a new line.
// State:
int offset = LineDocumentUtils.getLineStart(ld, startOffset); // The line's offset
int end = endOffset;
// Pending - apply comment formatting too?
// XXX Look up RHTML too
//int indentSize = EditorOptions.get(RubyInstallation.RUBY_MIME_TYPE).getSpacesPerTab();
//int hangingIndentSize = indentSize;
// Build up a set of offsets and indents for lines where I know I need
// to adjust the offset. I will then go back over the document and adjust
// lines that are different from the intended indent. By doing piecemeal
// replacements in the document rather than replacing the whole thing,
// a lot of things will work better: breakpoints and other line annotations
// will be left in place, semantic coloring info will not be temporarily
// damaged, and the caret will stay roughly where it belongs.
// The token balance at the offset
int balance = 0;
// The bracket balance at the offset ( parens, bracket, brace )
int bracketBalance = 0;
boolean continued = false;
// boolean indentHtml = false;
// if (embeddedJavaScript) {
// indentHtml = codeStyle.indentHtml();
// }
//int originallockCommentIndention = 0;
int adjustedBlockCommentIndention = 0;
int endIndents;
final int IN_CODE = 0;
final int IN_LITERAL = 1;
final int IN_BLOCK_COMMENT_START = 2;
final int IN_BLOCK_COMMENT_MIDDLE = 3;
// this cycle is written in offset but in fact it iretates over lines
while ((!includeEnd && offset < end) || (includeEnd && offset <= end)) {
int indent; // The indentation to be used for the current line
if (context.isEmbedded()) {
// now using JavaScript indent size to indent from <SCRIPT> tag; should it be HTML?
initialIndent = context.getEmbeddedIndent() + indentSize;
}
int lineType = IN_CODE;
int pos = LineDocumentUtils.getLineFirstNonWhitespace(ld, offset);
TokenSequence<?extends JsTokenId> ts = null;
if (pos != -1) {
// I can't look at the first position on the line, since
// for a string array that is indented, the indentation portion
// is recorded as a blank identifier
ts = LexUtilities.getPositionedSequence(doc, pos, false, language);
if (ts != null) {
JsTokenId id = ts.token().id();
int index = ts.index();
JsTokenId previousId = null;
if (ts.movePrevious()) {
Token<? extends JsTokenId> previous = LexUtilities.findPreviousNonWsNonComment(ts);
if (previous != null) {
previousId = previous.id();
}
ts.moveIndex(index);
ts.moveNext();
}
// We don't have multiline string literals in JavaScript!
if (id == JsTokenId.BLOCK_COMMENT || id == JsTokenId.DOC_COMMENT) {
if (ts.offset() == pos) {
lineType = IN_BLOCK_COMMENT_START;
//originallockCommentIndention = GsfUtilities.getLineIndent(doc, offset);
} else {
lineType = IN_BLOCK_COMMENT_MIDDLE;
}
} else if (LexUtilities.isBinaryOperator(id, previousId)) {
// If a line starts with a non unary operator we can
// assume it's a continuation from a previous line
continued = true;
} else if (id == JsTokenId.STRING || id == JsTokenId.STRING_END ||
id == JsTokenId.TEMPLATE || id == JsTokenId.TEMPLATE_END ||
id == JsTokenId.REGEXP || id == JsTokenId.REGEXP_END) {
// You can get multiline literals in JavaScript by inserting a \ at the end
// of the line
lineType = IN_LITERAL;
}
} else {
// No ruby token -- leave the formatting alone!
// (Probably in an RHTML file on a line with no JavaScript)
lineType = IN_LITERAL;
}
}
int hangingIndent = continued ? (continuationIndent) : 0;
if (lineType == IN_LITERAL) {
// Skip this line - leave formatting as it is prior to reformatting
indent = GsfUtilities.getLineIndent(doc, offset);
// No compound indent for JavaScript
// if (embeddedJavaScript && indentHtml && balance > 0) {
// indent += balance * indentSize;
// }
} else if (lineType == IN_BLOCK_COMMENT_MIDDLE) {
if (doc.getText(pos,1).charAt(0) == '*') {
// *-lines get indented to be flushed with the * in /*, other lines
// get indented to be aligned with the presumably indented text content!
//indent = LexUtilities.getLineIndent(doc, ts.offset())+1;
indent = adjustedBlockCommentIndention+1;
} else {
// Leave indentation of comment blocks alone since they probably correspond
// to commented out code - we don't want to lose the indentation.
// Possibly, I could shift the code all relative to the first line
// in the commented out block... A possible later enhancement.
// This shifts by the starting line which is wrong - should use the first comment line
//indent = LexUtilities.getLineIndent(doc, offset)-originallockCommentIndention+adjustedBlockCommentIndention;
indent = GsfUtilities.getLineIndent(doc, offset);
}
} else if ((!indentOnly || offset < context.getCaretLineStart() || offset > context.getCaretLineEnd()) && (endIndents = isEndIndent(context, offset)) > 0) {
indent = (balance-endIndents) * indentSize + hangingIndent + initialIndent;
} else {
assert lineType == IN_CODE || lineType == IN_BLOCK_COMMENT_START;
indent = balance * indentSize + hangingIndent + initialIndent;
// System.out.println("### indent " + indent + " = " + balance + " * " + indentSize + " + " + hangingIndent + " + " + initialIndent);
if (lineType == IN_BLOCK_COMMENT_START) {
adjustedBlockCommentIndention = indent;
}
}
if (indent < 0) {
indent = 0;
}
int lineBegin = LineDocumentUtils.getLineFirstNonWhitespace(ld, offset);
// Insert whitespace on empty lines too -- needed for abbreviations expansion
if (lineBegin != -1 || indentEmptyLines) {
// Don't do a hanging indent if we're already indenting beyond the parent level?
context.addIndentation(new IndentContext.Indentation(offset, indent, continued));
}
int endOfLine = LineDocumentUtils.getLineEnd(ld, offset) + 1;
if (lineBegin != -1) {
balance += getTokenBalance(context, ts, lineBegin, endOfLine, true, indentOnly);
int bracketDelta = getTokenBalance(context, ts, lineBegin, endOfLine, false, indentOnly);
bracketBalance += bracketDelta;
continued = isContinuation(doc, offset, bracketBalance, continued, bracketDelta, context.getBlocks().isEmpty() ? null : context.getBlocks().peek());
}
offset = endOfLine;
}
} catch (BadLocationException ble) {
Exceptions.printStackTrace(ble);
}
}
private int getTokenBalance(IndentContext context, TokenSequence<? extends JsTokenId> ts, int begin, int end, boolean includeKeywords, boolean indentOnly) {
int balance = 0;
Document doc = context.getDocument();
if (ts == null) {
// remember indent of previous html tag
context.setEmbeddedIndent(GsfUtilities.getLineIndent(doc, begin));
return 0;
}
ts.move(begin);
if (!ts.moveNext()) {
return 0;
}
int last = begin;
do {
Token<? extends JsTokenId> token = ts.token();
if (token == null) {
break;
}
JsTokenId id = token.id();
if (includeKeywords) {
int delta = getTokenBalanceDelta(context, id, ts, indentOnly);
balance += delta;
} else {
balance += getBracketBalanceDelta(id);
}
last = ts.offset() + token.length();
} while (ts.moveNext() && (ts.offset() < end));
if (context.isEmbedded() && last < end) {
// We're not done yet... find the next section...
TokenSequence<? extends JsTokenId> ets = LexUtilities.getNextJsTokenSequence(doc, last+1, end, language);
if (ets != null && ets.offset() > begin) {
return balance + getTokenBalance(context, ets, ets.offset(), end, includeKeywords, indentOnly);
}
}
return balance;
}
private int getBracketBalanceDelta(JsTokenId id) {
if (id == JsTokenId.BRACKET_LEFT_PAREN || id == JsTokenId.BRACKET_LEFT_BRACKET) {
return 1;
} else if (id == JsTokenId.BRACKET_RIGHT_PAREN || id == JsTokenId.BRACKET_RIGHT_BRACKET) {
return -1;
}
return 0;
}
private int getTokenBalanceDelta(IndentContext context, JsTokenId id, TokenSequence<? extends JsTokenId> ts, boolean indentOnly) {
try {
Document doc = context.getDocument();
LineDocument ld = LineDocumentUtils.asRequired(doc, LineDocument.class);
OffsetRange range;
if (id == JsTokenId.BRACKET_LEFT_BRACKET) {
// block with braces, just record it to stack and return 1
context.getBlocks().push(new IndentContext.BlockDescription(
false, false, new OffsetRange(ts.offset(), ts.offset())));
return 1;
} else if (id == JsTokenId.BRACKET_LEFT_CURLY) {
// block with braces, just record it to stack and return 1
// also check and mark if it is object literal block
boolean object = false;
TokenSequence<? extends JsTokenId> inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
if (inner != null) {
inner.movePrevious();
Token<? extends JsTokenId> token = LexUtilities.findPreviousNonWsNonComment(inner);
// simple detection of "function foo() {" and "function () {"
// "if () {", "with () {", "catch () {", "finally {"
// "do {", "while () {", "for () {"
if (token.id() != JsTokenId.BRACKET_RIGHT_PAREN
&& token.id() != JsTokenId.KEYWORD_DO
&& token.id() != JsTokenId.KEYWORD_ELSE
&& token.id() != JsTokenId.KEYWORD_FINALLY) {
object = true;
}
}
context.getBlocks().push(new IndentContext.BlockDescription(
false, object, new OffsetRange(ts.offset(), ts.offset())));
return 1;
} else if (id == JsTokenId.KEYWORD_CASE || id == JsTokenId.KEYWORD_DEFAULT) {
int index = ts.index();
// find colon ':'
LexUtilities.findNextIncluding(ts, Collections.singletonList(JsTokenId.OPERATOR_COLON));
// skip whitespaces, comments and newlines
Token<? extends JsTokenId> token = LexUtilities.findNextNonWsNonComment(ts);
JsTokenId tokenId = token.id();
if (tokenId == JsTokenId.KEYWORD_CASE || tokenId == JsTokenId.KEYWORD_DEFAULT) {
return 0;
} else if (tokenId == JsTokenId.BRACKET_RIGHT_CURLY) {
return -1;
} else {
// look at the beginning of next line if there is case or default
LexUtilities.findNextIncluding(ts, Collections.singletonList(JsTokenId.EOL));
LexUtilities.findNextNonWsNonComment(ts);
if (ts.token().id() == JsTokenId.KEYWORD_CASE || ts.token().id() == JsTokenId.KEYWORD_DEFAULT
|| ts.token().id() == JsTokenId.BRACKET_LEFT_CURLY) {
return 0;
}
}
ts.moveIndex(index);
ts.moveNext();
return 1;
} else if (id == JsTokenId.BRACKET_RIGHT_BRACKET || id == JsTokenId.BRACKET_RIGHT_CURLY) {
/*
* End of braces block.
* If we are not on same line where block started, try to push
* all braceless blocks from stack and decrease indent for them,
* otherwise just decrese indent by 1.
* For example:
* if (true)
* if (true)
* if (true)
* foo(); // we should decrease indent by 3 levels
*
* but:
* if (true)
* if (true)
* if (map[0]) // at ']' we should decrease only by 1
* foo();
*/
int delta = -1;
IndentContext.BlockDescription lastPop = context.getBlocks().empty() ? null : context.getBlocks().pop();
if (lastPop != null && lastPop.getRange().getStart() <= (doc.getLength() + 1)
&& LineDocumentUtils.getLineIndex(ld, lastPop.getRange().getStart()) != LineDocumentUtils.getLineIndex(ld, ts.offset())) {
int blocks = 0;
while (!context.getBlocks().empty() && context.getBlocks().peek().isBraceless()) {
context.getBlocks().pop();
blocks++;
}
delta -= blocks;
}
return delta;
} else if ((range = LexUtilities.getMultilineRange(doc, ts)) != OffsetRange.NONE) {
// we found braceless block, let's record it in the stack
context.getBlocks().push(new IndentContext.BlockDescription(true, false, range));
} else if (id == JsTokenId.EOL) {
if (!indentOnly) {
TokenSequence<? extends JsTokenId> inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
// skip whitespaces and newlines
Token<? extends JsTokenId> nextToken = null;
if (inner != null) {
nextToken = LexUtilities.findNextNonWsNonComment(inner);
}
TokenId tokenId = nextToken == null ? null : nextToken.id();
if (tokenId == JsTokenId.BRACKET_RIGHT_CURLY) {
// if it is end of 'switch'
OffsetRange offsetRange = LexUtilities.findBwd(doc, inner, JsTokenId.BRACKET_LEFT_CURLY, JsTokenId.BRACKET_RIGHT_CURLY);
if (offsetRange != OffsetRange.NONE) {
inner.movePrevious();
if (LexUtilities.skipParenthesis(inner, true)) {
Token<? extends JsTokenId> token = inner.token();
token = LexUtilities.findPreviousNonWsNonComment(inner);
if (token.id() == JsTokenId.KEYWORD_SWITCH) {
return -1;
}
}
}
} else if (tokenId == JsTokenId.KEYWORD_CASE || tokenId == JsTokenId.KEYWORD_DEFAULT) {
inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
Token<? extends JsTokenId> prevToken = LexUtilities.findPreviousNonWsNonComment(inner);
if (prevToken.id() != JsTokenId.BRACKET_LEFT_CURLY) {
// it must be case or default
inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
LexUtilities.findPreviousIncluding(inner,
Arrays.asList(JsTokenId.KEYWORD_CASE, JsTokenId.KEYWORD_DEFAULT));
int offset = inner.offset();
inner = LexUtilities.getPositionedSequence(doc, ts.offset(), language);
prevToken = LexUtilities.findPrevious(inner, Arrays.asList(JsTokenId.WHITESPACE, JsTokenId.EOL));
int beginLine = LineDocumentUtils.getLineIndex(ld, offset);
int eolLine = LineDocumentUtils.getLineIndex(ld, ts.offset());
// we need to take care of case like this:
// case 'a':
// test();
// break;
//
// //comment
//
// case 'b':
// test();
// break;
// note the comment - we would get to this block twice
// (eol after break and eol after //comment)
// so indentation level change would be -2 instead of -1
if (prevToken.id() != JsTokenId.BLOCK_COMMENT
&& prevToken.id() != JsTokenId.DOC_COMMENT
&& prevToken.id() != JsTokenId.LINE_COMMENT) {
if (beginLine != eolLine) {
if (prevToken.id() == JsTokenId.BRACKET_RIGHT_CURLY) {
OffsetRange offsetRange = LexUtilities.findBwd(doc, inner, JsTokenId.BRACKET_LEFT_CURLY, JsTokenId.BRACKET_RIGHT_CURLY);
if (offsetRange != OffsetRange.NONE) {
inner.movePrevious();
Token<? extends JsTokenId> token = LexUtilities.findPreviousNonWsNonComment(inner);
// if this is not the whole case in block
// case 's':
// {
// something();
// }
if (token.id() != JsTokenId.OPERATOR_COLON) {
return -1;
}
}
} else {
return -1;
}
}
} else {
int commentLine = LineDocumentUtils.getLineIndex(ld, inner.offset());
if (beginLine != eolLine && commentLine == beginLine) {
return -1;
}
}
}
}
}
// other
if (!context.getBlocks().empty()) {
if (context.getBlocks().peek().isBraceless()) {
// end of line after braceless block start
OffsetRange stackOffset = context.getBlocks().peek().getRange();
if (stackOffset.containsInclusive(ts.offset())) {
if (indentOnly) {
// enter pressed in braceless block
return 1;
}
// we are in the braceless block statement
int stackEndLine = LineDocumentUtils.getLineIndex(ld, stackOffset.getEnd());
int offsetLine = LineDocumentUtils.getLineIndex(ld, ts.offset());
if (stackEndLine == offsetLine) {
// if we are at the last line of braceless block statement
// increse indent by 1
return 1;
}
} else {
// we are not in braceless block statement,
// let's decrease indent for all braceless blocks in top of stack (if any)
int blocks = 0;
while (!context.getBlocks().empty() && context.getBlocks().peek().isBraceless()) {
blocks++;
context.getBlocks().pop();
}
return -blocks;
}
}
}
}
} catch (BadLocationException ble) {
LOGGER.log(Level.INFO, null, ble);
}
return 0;
}
private int isEndIndent(IndentContext context, int offset) throws BadLocationException {
Document doc = context.getDocument();
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld == null) {
return 0;
}
int lineBegin = LineDocumentUtils.getLineFirstNonWhitespace(ld, offset);
if (lineBegin != -1) {
Token<?extends JsTokenId> token = getFirstToken(context, offset);
if (token == null) {
return 0;
}
TokenId id = token.id();
// If the line starts with an end-marker, such as "end", "}", "]", etc.,
// find the corresponding opening marker, and indent the line to the same
// offset as the beginning of that line.
if (id == JsTokenId.BRACKET_RIGHT_CURLY || id == JsTokenId.BRACKET_RIGHT_BRACKET
/*|| id == JsTokenId.BRACKET_RIGHT_PAREN*/) {
int indents = 1;
// Check if there are multiple end markers here... if so increase indent level.
// This should really do an iteration... for now just handling the most common
// scenario in JavaScript where we have }) in object literals
int lineEnd = LineDocumentUtils.getLineEnd(ld, offset);
int newOffset = offset;
while (newOffset < lineEnd && token != null) {
newOffset += token.length();
if (newOffset < doc.getLength()) {
token = LexUtilities.getToken(doc, newOffset, language);
if (token != null) {
id = token.id();
if (id == JsTokenId.WHITESPACE) {
continue;
} else {
break;
}
}
}
}
return indents;
}
}
return 0;
}
private Token<? extends JsTokenId> getFirstToken(IndentContext context, int offset) throws BadLocationException {
Document doc = context.getDocument();
LineDocument ld = LineDocumentUtils.as(doc, LineDocument.class);
if (ld == null) {
return null;
}
int lineBegin = LineDocumentUtils.getLineFirstNonWhitespace(ld, offset);
if (lineBegin != -1) {
if (context.isEmbedded()) {
TokenSequence<? extends JsTokenId> ts = LexUtilities.getTokenSequence(
TokenHierarchy.get(doc), lineBegin, language);
if (ts != null) {
ts.moveNext();
Token<? extends JsTokenId> token = ts.token();
while (token != null && token.id() == JsTokenId.WHITESPACE) {
if (!ts.moveNext()) {
return null;
}
token = ts.token();
}
return token;
}
} else {
return LexUtilities.getToken(doc, lineBegin, language);
}
}
return null;
}
/**
* Iterates tokens from token to limit while properly updating indentation
* level. Returns the new index in token sequence.
*
* @param token start token
* @param index start index
* @param limit end token
* @param formatContext context to update
* @return the new index
*/
private void moveForward(FormatToken token, FormatToken limit,
FormatContext formatContext, CodeStyle.Holder codeStyle, boolean allowComment) {
for (FormatToken current = token; current != null && current != limit; current = current.next()) {
assert current.isVirtual()
|| current.getKind() == FormatToken.Kind.WHITESPACE
|| current.getKind() == FormatToken.Kind.EOL
|| allowComment
&& (current.getKind() == FormatToken.Kind.BLOCK_COMMENT
|| current.getKind() == FormatToken.Kind.LINE_COMMENT
|| current.getKind() == FormatToken.Kind.DOC_COMMENT): current;
processed.add(current);
updateIndentationLevel(current, formatContext, codeStyle);
if (current.getKind() == FormatToken.Kind.EOL) {
formatContext.setCurrentLineStart(current.getOffset()
+ 1 + formatContext.getOffsetDiff());
formatContext.setLastLineWrap(null);
}
}
}
private static boolean isWhitespace(CharSequence charSequence) {
for (int i = 0; i < charSequence.length(); i++) {
if (!Character.isWhitespace(charSequence.charAt(i))) {
return false;
}
}
return true;
}
private static boolean isSingleLineEmbedding(FormatToken token, Document doc, Snapshot snapshot) {
final FormatToken prevNonVirtual = FormatTokenStream.getPreviousNonVirtual(token);
if (prevNonVirtual != null) {
final int originalOffset = snapshot.getOriginalOffset(prevNonVirtual.getOffset());
final List<TokenSequence<?>> tokenSeqs = TokenHierarchy.get(doc)
.embeddedTokenSequences(originalOffset, false);
final String snapshotMimePath = snapshot.getMimePath().getPath();
for (TokenSequence<?> ts : tokenSeqs) {
if (ts.languagePath().mimePath()
.concat("/") //NOI18N
.concat(snapshot.getMimeType()).equals(snapshotMimePath)) {
ts.move(originalOffset);
ts.moveNext();
if (ts.token() != null) {
final String tokenText = ts.token().text().toString();
// if text of the token already contains newline char
// it is not single-line embedding
return !tokenText.contains("\n"); //NOI18N
}
return false;
}
}
}
return false;
}
static class Indentation {
static final Indentation ALLOWED = new Indentation(true, false);
static final Indentation FORBIDDEN = new Indentation(false, false);
private final boolean allowed;
private final boolean exceedLimits;
public Indentation(boolean allowed, boolean exceedLimits) {
this.allowed = allowed;
this.exceedLimits = exceedLimits;
}
public boolean isAllowed() {
return allowed;
}
public boolean isExceedLimits() {
return exceedLimits;
}
}
}