blob: 61415eb90ce4078b49a50edfc8a7197752d28d76 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* 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.intellij.openapi.editor.impl;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import gnu.trove.TObjectIntHashMap;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NotNull;
/**
* Not thread-safe. Performs caching of char widths, so cache reset must be invoked (via {@link #clearSymbolWidthCache()} method) when
* font settings are changed in editor.
*
* @author Denis Zhdanov
* @since Jul 27, 2010 4:06:27 PM
*/
public class DefaultEditorTextRepresentationHelper implements EditorTextRepresentationHelper {
/**
* We don't expect the user to have too many different font sizes and font types within the editor, however, need to
* provide a defense from unlimited cache growing.
*/
private static final int MAX_SYMBOLS_WIDTHS_CACHE_SIZE = 1000;
/** We cache symbol widths here because it's detected to be a bottleneck. */
private final TObjectIntHashMap<Key> mySymbolWidthCache = new TObjectIntHashMap<Key>();
private final Key mySharedKey = new Key();
/**
* This is performance-related optimization because profiling shows that it's rather expensive to call
* {@link Editor#getColorsScheme()} often due to contention in 'assert read access'.
*/
private final Editor myEditor;
public DefaultEditorTextRepresentationHelper(Editor editor) {
myEditor = editor;
}
@Override
public int toVisualColumnSymbolsNumber(@NotNull CharSequence text, int start, int end, int x) {
return EditorUtil.textWidthInColumns(myEditor, text, start, end, x);
}
@Override
public int charWidth(char c, int fontType) {
// Symbol width retrieval is detected to be a bottleneck, hence, we perform a caching here in assumption that every representation
// helper is editor-bound and cache size is not too big.
mySharedKey.fontType = fontType;
mySharedKey.c = c;
return charWidth(c);
}
@Override
public int textWidth(@NotNull CharSequence text, int start, int end, int fontType, int x) {
int startToUse = start;
for (int i = end - 1; i >= start; i--) {
if (text.charAt(i) == '\n') {
startToUse = i + 1;
break;
}
}
int result = 0;
// Symbol width retrieval is detected to be a bottleneck, hence, we perform a caching here in assumption that every representation
// helper is editor-bound and cache size is not too big.
mySharedKey.fontType = fontType;
for (int i = startToUse; i < end; i++) {
char c = text.charAt(i);
if (c != '\t') {
mySharedKey.c = c;
result += charWidth(c);
continue;
}
result += EditorUtil.nextTabStop(x + result, myEditor) - result - x;
}
return result;
}
private int charWidth(char c) {
int result = mySymbolWidthCache.get(mySharedKey);
if (result > 0) {
return result;
}
Key key = mySharedKey.clone();
FontInfo font = ComplementaryFontsRegistry.getFontAbleToDisplay(c, key.fontType, myEditor.getColorsScheme().getFontPreferences());
result = font.charWidth(c);
if (mySymbolWidthCache.size() >= MAX_SYMBOLS_WIDTHS_CACHE_SIZE) {
// Don't expect to be here.
mySymbolWidthCache.clear();
}
mySymbolWidthCache.put(key, result);
return result;
}
public void clearSymbolWidthCache() {
mySymbolWidthCache.clear();
}
private static class Key {
@JdkConstants.FontStyle private int fontType;
private char c;
private Key() {
this(0, ' ');
}
Key(@JdkConstants.FontStyle int fontType, char c) {
this.fontType = fontType;
this.c = c;
}
@Override
protected Key clone() {
return new Key(fontType, c);
}
@Override
public int hashCode() {
int result = fontType;
result = 31 * result + c;
return result;
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Key key = (Key)o;
if (fontType != key.fontType) return false;
if (c != key.c) return false;
return true;
}
}
}