forgot to check length()
[idea/community.git] / platform / lang-impl / src / com / intellij / application / options / colors / SimpleEditorPreview.java
1 /*
2  * Copyright 2000-2014 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16
17 package com.intellij.application.options.colors;
18
19 import com.intellij.application.options.colors.highlighting.HighlightData;
20 import com.intellij.application.options.colors.highlighting.HighlightsExtractor;
21 import com.intellij.ide.highlighter.HighlighterFactory;
22 import com.intellij.openapi.editor.Editor;
23 import com.intellij.openapi.editor.EditorFactory;
24 import com.intellij.openapi.editor.LogicalPosition;
25 import com.intellij.openapi.editor.ScrollType;
26 import com.intellij.openapi.editor.colors.CodeInsightColors;
27 import com.intellij.openapi.editor.colors.EditorColorsScheme;
28 import com.intellij.openapi.editor.colors.TextAttributesKey;
29 import com.intellij.openapi.editor.event.CaretAdapter;
30 import com.intellij.openapi.editor.event.CaretEvent;
31 import com.intellij.openapi.editor.event.CaretListener;
32 import com.intellij.openapi.editor.ex.EditorEx;
33 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
34 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
35 import com.intellij.openapi.fileTypes.SyntaxHighlighter;
36 import com.intellij.openapi.options.colors.ColorSettingsPage;
37 import com.intellij.openapi.options.colors.EditorHighlightingProvidingColorSettingsPage;
38 import com.intellij.psi.tree.IElementType;
39 import com.intellij.util.Alarm;
40 import com.intellij.util.EventDispatcher;
41 import com.intellij.util.ui.UIUtil;
42 import org.jetbrains.annotations.NotNull;
43
44 import javax.swing.*;
45 import java.awt.*;
46 import java.awt.event.MouseEvent;
47 import java.awt.event.MouseMotionAdapter;
48 import java.util.*;
49 import java.util.List;
50
51 public class SimpleEditorPreview implements PreviewPanel{
52   private final ColorSettingsPage myPage;
53
54   private final EditorEx myEditor;
55   private final Alarm myBlinkingAlarm;
56   private final HighlightData[] myHighlightData;
57
58   private final ColorAndFontOptions myOptions;
59
60   private final EventDispatcher<ColorAndFontSettingsListener> myDispatcher = EventDispatcher.create(ColorAndFontSettingsListener.class);
61
62   public SimpleEditorPreview(final ColorAndFontOptions options, final ColorSettingsPage page) {
63     this(options, page, true);
64   }
65
66   public SimpleEditorPreview(final ColorAndFontOptions options, final ColorSettingsPage page, final boolean navigatable) {
67     myOptions = options;
68     myPage = page;
69
70     String text = page.getDemoText();
71
72     HighlightsExtractor extractant2 = new HighlightsExtractor(page.getAdditionalHighlightingTagToDescriptorMap());
73     List<HighlightData> highlights = new ArrayList<>();
74     String stripped = extractant2.extractHighlights(text, highlights);
75     myHighlightData = highlights.toArray(new HighlightData[highlights.size()]);
76     int selectedLine = -1;
77     myEditor = (EditorEx)FontEditorPreview.createPreviewEditor(stripped, 10, 3, selectedLine, myOptions, false);
78
79     FontEditorPreview.installTrafficLights(myEditor);
80     myBlinkingAlarm = new Alarm().setActivationComponent(myEditor.getComponent());
81     if (navigatable) {
82       addMouseMotionListener(myEditor, page.getHighlighter(), myHighlightData, false);
83
84       CaretListener listener = new CaretAdapter() {
85         @Override
86         public void caretPositionChanged(CaretEvent e) {
87           navigate(myEditor, true, e.getNewPosition(), page.getHighlighter(), myHighlightData, false);
88         }
89       };
90       myEditor.getCaretModel().addCaretListener(listener);
91     }
92   }
93
94   public EditorEx getEditor() {
95     return myEditor;
96   }
97
98   private void addMouseMotionListener(final Editor view,
99                                       final SyntaxHighlighter highlighter,
100                                       final HighlightData[] data, final boolean isBackgroundImportant) {
101     view.getContentComponent().addMouseMotionListener(new MouseMotionAdapter() {
102       @Override
103       public void mouseMoved(MouseEvent e) {
104         LogicalPosition pos = view.xyToLogicalPosition(new Point(e.getX(), e.getY()));
105         navigate(view, false, pos, highlighter, data, isBackgroundImportant);
106       }
107     });
108   }
109
110   private void navigate(final Editor editor, boolean select,
111                         LogicalPosition pos,
112                         final SyntaxHighlighter highlighter,
113                         final HighlightData[] data, final boolean isBackgroundImportant) {
114     int offset = editor.logicalPositionToOffset(pos);
115
116     if (!isBackgroundImportant && editor.offsetToLogicalPosition(offset).column != pos.column) {
117       if (!select) {
118         ClickNavigator.setCursor(editor, Cursor.TEXT_CURSOR);
119         return;
120       }
121     }
122
123     if (data != null) {
124       for (HighlightData highlightData : data) {
125         if (ClickNavigator.highlightDataContainsOffset(highlightData, editor.logicalPositionToOffset(pos))) {
126           if (!select) {
127             ClickNavigator.setCursor(editor, Cursor.HAND_CURSOR);
128           }
129           else {
130             myDispatcher.getMulticaster().selectionInPreviewChanged(highlightData.getHighlightType());
131           }
132           return;
133         }
134       }
135     }
136
137     if (highlighter != null) {
138       HighlighterIterator itr = ((EditorEx)editor).getHighlighter().createIterator(offset);
139       selectItem(itr, highlighter, select);
140       ClickNavigator.setCursor(editor, select ? Cursor.TEXT_CURSOR : Cursor.HAND_CURSOR);
141     }
142   }
143
144   private void selectItem(HighlighterIterator itr, SyntaxHighlighter highlighter, final boolean select) {
145     IElementType tokenType = itr.getTokenType();
146     if (tokenType == null) return;
147     String type = ClickNavigator.highlightingTypeFromTokenType(tokenType, highlighter);
148     if (select) {
149       myDispatcher.getMulticaster().selectionInPreviewChanged(type);
150     }
151   }
152
153   @Override
154   public JComponent getPanel() {
155     return myEditor.getComponent();
156   }
157
158   @Override
159   public void updateView() {
160     EditorColorsScheme scheme = myOptions.getSelectedScheme();
161
162     myEditor.setColorsScheme(scheme);
163
164     EditorHighlighter highlighter = null;
165     if (myPage instanceof EditorHighlightingProvidingColorSettingsPage) {
166
167       highlighter = ((EditorHighlightingProvidingColorSettingsPage)myPage).createEditorHighlighter(scheme);
168     }
169     if (highlighter == null) {
170       final SyntaxHighlighter pageHighlighter = myPage.getHighlighter();
171       highlighter = HighlighterFactory.createHighlighter(pageHighlighter, scheme);
172     }
173     myEditor.setHighlighter(highlighter);
174     updateHighlighters();
175
176     myEditor.reinitSettings();
177
178   }
179
180   private void updateHighlighters() {
181     UIUtil.invokeLaterIfNeeded(() -> {
182       if (myEditor.isDisposed()) return;
183       myEditor.getMarkupModel().removeAllHighlighters();
184       HighlightData[] datum = myHighlightData;
185       final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(myPage);
186       for (final HighlightData data : datum) {
187         data.addHighlToView(myEditor, myOptions.getSelectedScheme(), displayText);
188       }
189     });
190   }
191
192   private static final int BLINK_COUNT = 3 * 2;
193
194   @Override
195   public void blinkSelectedHighlightType(Object description) {
196     if (description instanceof EditorSchemeAttributeDescriptor){
197       String type = ((EditorSchemeAttributeDescriptor)description).getType();
198
199       List<HighlightData> highlights = startBlinkingHighlights(myEditor,
200                                                                myHighlightData, type,
201                                                                myPage.getHighlighter(), true,
202                                                                myBlinkingAlarm, BLINK_COUNT, myPage);
203
204       scrollHighlightInView(highlights, myEditor);
205     }
206   }
207
208   private static void scrollHighlightInView(final List<HighlightData> highlightDatas, final Editor editor) {
209     boolean needScroll = true;
210     int minOffset = Integer.MAX_VALUE;
211     for(HighlightData data: highlightDatas) {
212       if (isOffsetVisible(editor, data.getStartOffset())) {
213         needScroll = false;
214         break;
215       }
216       minOffset = Math.min(minOffset, data.getStartOffset());
217     }
218     if (needScroll && minOffset != Integer.MAX_VALUE) {
219       LogicalPosition pos = editor.offsetToLogicalPosition(minOffset);
220       editor.getScrollingModel().scrollTo(pos, ScrollType.MAKE_VISIBLE);
221     }
222   }
223
224   private static boolean isOffsetVisible(final Editor editor, final int startOffset) {
225     Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
226     Point point = editor.logicalPositionToXY(editor.offsetToLogicalPosition(startOffset));
227     return point.y >= visibleArea.y && point.y < (visibleArea.y + visibleArea.height);
228   }
229
230   private void stopBlinking() {
231     myBlinkingAlarm.cancelAllRequests();
232   }
233
234   private List<HighlightData> startBlinkingHighlights(final EditorEx editor,
235                                                       final HighlightData[] highlightDatum,
236                                                       final String attrKey,
237                                                       final SyntaxHighlighter highlighter,
238                                                       final boolean show,
239                                                       final Alarm alarm,
240                                                       final int count,
241                                                       final ColorSettingsPage page) {
242     if (show && count <= 0) return Collections.emptyList();
243     editor.getMarkupModel().removeAllHighlighters();
244     boolean found = false;
245     List<HighlightData> highlights = new ArrayList<>();
246     List<HighlightData> matchingHighlights = new ArrayList<>();
247     for (int i = 0; highlightDatum != null && i < highlightDatum.length; i++) {
248       HighlightData highlightData = highlightDatum[i];
249       String type = highlightData.getHighlightType();
250       highlights.add(highlightData);
251       if (show && type.equals(attrKey)) {
252         highlightData =
253         new HighlightData(highlightData.getStartOffset(), highlightData.getEndOffset(),
254                           CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
255         highlights.add(highlightData);
256         matchingHighlights.add(highlightData);
257         found = true;
258       }
259     }
260     if (!found && highlighter != null) {
261       HighlighterIterator iterator = editor.getHighlighter().createIterator(0);
262       do {
263         IElementType tokenType = iterator.getTokenType();
264         TextAttributesKey[] tokenHighlights = highlighter.getTokenHighlights(tokenType);
265         for (final TextAttributesKey tokenHighlight : tokenHighlights) {
266           String type = tokenHighlight.getExternalName();
267           if (show && type != null && type.equals(attrKey)) {
268             HighlightData highlightData = new HighlightData(iterator.getStart(), iterator.getEnd(),
269                                                             CodeInsightColors.BLINKING_HIGHLIGHTS_ATTRIBUTES);
270             highlights.add(highlightData);
271             matchingHighlights.add(highlightData);
272           }
273         }
274         iterator.advance();
275       }
276       while (!iterator.atEnd());
277     }
278
279     final Map<TextAttributesKey, String> displayText = ColorSettingsUtil.keyToDisplayTextMap(page);
280
281     // sort highlights to avoid overlappings
282     Collections.sort(highlights, (highlightData1, highlightData2) -> highlightData1.getStartOffset() - highlightData2.getStartOffset());
283     for (int i = highlights.size() - 1; i >= 0; i--) {
284       HighlightData highlightData = highlights.get(i);
285       int startOffset = highlightData.getStartOffset();
286       HighlightData prevHighlightData = i == 0 ? null : highlights.get(i - 1);
287       if (prevHighlightData != null
288           && startOffset <= prevHighlightData.getEndOffset()
289           && highlightData.getHighlightType().equals(prevHighlightData.getHighlightType())) {
290         prevHighlightData.setEndOffset(highlightData.getEndOffset());
291       }
292       else {
293         highlightData.addHighlToView(editor, myOptions.getSelectedScheme(), displayText);
294       }
295     }
296     alarm.cancelAllRequests();
297     alarm.addComponentRequest(() -> startBlinkingHighlights(editor, highlightDatum, attrKey, highlighter, !show, alarm, count - 1, page), 400);
298     return matchingHighlights;
299   }
300
301
302   @Override
303   public void addListener(@NotNull final ColorAndFontSettingsListener listener) {
304     myDispatcher.addListener(listener);
305   }
306
307   @Override
308   public void disposeUIResources() {
309     EditorFactory editorFactory = EditorFactory.getInstance();
310     editorFactory.releaseEditor(myEditor);
311     stopBlinking();
312
313   }
314 }