// Copyright 2012 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package com.google.collide.codemirror2;
import com.google.collide.json.shared.JsonArray;
import com.google.collide.json.shared.JsonStringMap;
import com.google.collide.shared.Pair;
import com.google.collide.shared.util.JsonCollections;
import com.google.collide.shared.util.StringUtils;
import com.google.common.annotations.VisibleForTesting;
import javax.annotation.Nonnull;
import javax.annotation.Nullable;
/**
* Codemirror2 {@link Token} related utilities.
*/
public class TokenUtil {
/**
* Map ["mode:width"] -> whitespace token of given mode and width.
*
* <p>Used by {@link #getPlaceholderForMode} to cache placeholder tokens.
*/
private static final JsonStringMap<Token> cachedPlaceholders = JsonCollections.createMap();
// Do not instantiate.
private TokenUtil() {}
/**
* <p>Finds the mode (programming language) corresponding to the given column:
* <ul>
* <li>If the column is inside a token then the mode of the token is returned
* <li>if the column is on the boundary between two tokens then the mode of
* the first token is returned (this behavior works well for auto-completion)
* <li>if the column is greater than the sum of all token lengths then the
* mode of the last token is returned (so that we handle auto-completion for
* fast typer in the best possible way)
* <li>if the map is empty then null is returned
* <li>if the column = 0 then the mode of the first tag is returned
* <li>if the column is < 0 then IllegalArgumentException is thrown
* </ul>
*
* @param initialMode mode before the first token.
* @param modes "language map" built by {@link #buildModes}
* @param column column to search for
* @return the mode of the token covering the column
* @throws IllegalArgumentException if the column is < 0
*/
public static String findModeForColumn(
String initialMode, @Nonnull JsonArray<Pair<Integer, String>> modes, int column) {
if (column < 0) {
throw new IllegalArgumentException("Column should be >= 0 but was " + column);
}
String mode = initialMode;
for (Pair<Integer, String> pair : modes.asIterable()) {
if (pair.first >= column) {
// We'll use last remembered mode.
break;
}
mode = pair.second;
}
return mode;
}
/**
* Builds a "language map".
*
* <p>Language map is an array of pairs {@code (column, mode)}, where column
* is the boundary between previous mode and current mode.
*
* <p>The array is ordered by the column.
*
* @param initialMode mode before the first token
* @param tokens tokens from which to build the map
* @return array of pairs (column, mode)
*/
@Nonnull
public static JsonArray<Pair<Integer, String>> buildModes(
@Nullable String initialMode, @Nonnull JsonArray<Token> tokens) {
JsonArray<Pair<Integer, String>> modes = JsonCollections.createArray();
String currentMode = initialMode;
int currentColumn = 0;
for (Token token : tokens.asIterable()) {
if (token.getType() == TokenType.NEWLINE) {
// Avoid "brandless" mode to be the last mode in the map.
break;
}
String mode = token.getMode();
if (!mode.equals(currentMode)) {
modes.add(new Pair<Integer, String>(currentColumn, mode));
currentMode = mode;
}
currentColumn += token.getValue().length();
}
return modes;
}
@VisibleForTesting
static void addPlaceholders(String mode, JsonStringMap<JsonArray<Token>> splitTokenMap,
int width) {
for (String key : splitTokenMap.getKeys().asIterable()) {
if (!key.equals(mode)) {
splitTokenMap.get(key).add(getPlaceholderForMode(key, width));
}
}
}
private static Token getPlaceholderForMode(String mode, int width) {
String key = mode + ":" + width;
if (cachedPlaceholders.containsKey(key)) {
return cachedPlaceholders.get(key);
}
Token token = new Token(mode, TokenType.WHITESPACE, StringUtils.getSpaces(width));
cachedPlaceholders.put(key, token);
return token;
}
}