summaryrefslogtreecommitdiff
path: root/platform/platform-impl/src/com/intellij/openapi/editor/impl/DefaultEditorTextRepresentationHelper.java
blob: 61415eb90ce4078b49a50edfc8a7197752d28d76 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/*
 * 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;
    }
  }
}