2 * Copyright 2000-2016 JetBrains s.r.o.
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
8 * http://www.apache.org/licenses/LICENSE-2.0
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.
16 package com.intellij.openapi.editor.impl.view;
18 import com.intellij.openapi.editor.*;
19 import com.intellij.openapi.editor.colors.EditorColors;
20 import com.intellij.openapi.editor.colors.EditorFontType;
21 import com.intellij.openapi.editor.ex.MarkupModelEx;
22 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
23 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
24 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
25 import com.intellij.openapi.editor.impl.*;
26 import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
27 import com.intellij.openapi.editor.markup.*;
28 import com.intellij.openapi.project.Project;
29 import com.intellij.openapi.util.Couple;
30 import com.intellij.openapi.util.SystemInfo;
31 import com.intellij.openapi.util.TextRange;
32 import com.intellij.openapi.util.registry.Registry;
33 import com.intellij.openapi.vfs.VirtualFile;
34 import com.intellij.openapi.wm.impl.IdeBackgroundUtil;
35 import com.intellij.ui.ColorUtil;
36 import com.intellij.ui.Gray;
37 import com.intellij.ui.JBColor;
38 import com.intellij.util.Processor;
39 import com.intellij.util.ui.JBUI;
40 import com.intellij.util.ui.UIUtil;
41 import gnu.trove.TFloatArrayList;
42 import org.jetbrains.annotations.NotNull;
43 import org.jetbrains.annotations.Nullable;
47 import java.util.Collection;
48 import java.util.HashMap;
52 * Renders editor contents.
54 class EditorPainter implements TextDrawingCallback {
55 private static final Color CARET_LIGHT = Gray._255;
56 private static final Color CARET_DARK = Gray._0;
57 private static final Stroke IME_COMPOSED_TEXT_UNDERLINE_STROKE = new BasicStroke(1, BasicStroke.CAP_ROUND, BasicStroke.JOIN_ROUND, 0,
58 new float[]{0, 2, 0, 2}, 0);
59 private static final int CARET_DIRECTION_MARK_SIZE = 5;
61 private final EditorView myView;
62 private final EditorImpl myEditor;
63 private final Document myDocument;
65 EditorPainter(EditorView view) {
67 myEditor = view.getEditor();
68 myDocument = myEditor.getDocument();
71 void paint(Graphics2D g) {
72 Rectangle clip = g.getClipBounds();
74 if (myEditor.getContentComponent().isOpaque()) {
75 g.setColor(myEditor.getBackgroundColor());
76 g.fillRect(clip.x, clip.y, clip.width, clip.height);
79 if (paintPlaceholderText(g)) {
84 int startLine = myView.yToVisualLine(Math.max(clip.y, 0));
85 int endLine = myView.yToVisualLine(Math.max(clip.y + clip.height, 0));
86 int startOffset = myView.visualLineToOffset(startLine);
87 int endOffset = myView.visualLineToOffset(endLine + 1);
88 ClipDetector clipDetector = new ClipDetector(myEditor, clip);
90 paintBackground(g, clip, startLine, endLine);
91 paintRightMargin(g, clip);
92 paintCustomRenderers(g, startOffset, endOffset);
93 MarkupModelEx docMarkup = (MarkupModelEx)DocumentMarkupModel.forDocument(myDocument, myEditor.getProject(), true);
94 paintLineMarkersSeparators(g, clip, docMarkup, startOffset, endOffset);
95 paintLineMarkersSeparators(g, clip, myEditor.getMarkupModel(), startOffset, endOffset);
96 paintTextWithEffects(g, clip, startLine, endLine);
97 paintHighlightersAfterEndOfLine(g, docMarkup, startOffset, endOffset);
98 paintHighlightersAfterEndOfLine(g, myEditor.getMarkupModel(), startOffset, endOffset);
99 paintBorderEffect(g, clipDetector, myEditor.getHighlighter(), startOffset, endOffset);
100 paintBorderEffect(g, clipDetector, docMarkup, startOffset, endOffset);
101 paintBorderEffect(g, clipDetector, myEditor.getMarkupModel(), startOffset, endOffset);
105 paintComposedTextDecoration(g);
108 private boolean paintPlaceholderText(Graphics2D g) {
109 CharSequence hintText = myEditor.getPlaceholder();
110 EditorComponentImpl editorComponent = myEditor.getContentComponent();
111 if (myDocument.getTextLength() > 0 || hintText == null || hintText.length() == 0 ||
112 KeyboardFocusManager.getCurrentKeyboardFocusManager().getFocusOwner() == editorComponent &&
113 !myEditor.getShowPlaceholderWhenFocused()) {
117 hintText = SwingUtilities.layoutCompoundLabel(g.getFontMetrics(), hintText.toString(), null, 0, 0, 0, 0,
118 editorComponent.getBounds(), new Rectangle(), new Rectangle(), 0);
119 g.setColor(myEditor.getFoldingModel().getPlaceholderAttributes().getForegroundColor());
120 g.setFont(myEditor.getColorsScheme().getFont(EditorFontType.PLAIN));
121 g.drawString(hintText.toString(), 0, myView.getAscent());
125 private void paintRightMargin(Graphics g, Rectangle clip) {
126 EditorSettings settings = myEditor.getSettings();
127 Color rightMargin = myEditor.getColorsScheme().getColor(EditorColors.RIGHT_MARGIN_COLOR);
128 if (!settings.isRightMarginShown() || rightMargin == null) return;
130 int x = settings.getRightMargin(myEditor.getProject()) * myView.getPlainSpaceWidth();
131 g.setColor(rightMargin);
132 UIUtil.drawLine(g, x, clip.y, x, clip.y + clip.height);
135 private void paintBackground(Graphics2D g, Rectangle clip, int startVisualLine, int endVisualLine) {
136 int lineCount = myEditor.getVisibleLineCount();
138 final Map<Integer, Couple<Integer>> virtualSelectionMap = createVirtualSelectionMap(startVisualLine, endVisualLine);
139 final VisualPosition primarySelectionStart = myEditor.getSelectionModel().getSelectionStartPosition();
140 final VisualPosition primarySelectionEnd = myEditor.getSelectionModel().getSelectionEndPosition();
142 LineLayout prefixLayout = myView.getPrefixLayout();
143 if (startVisualLine == 0 && prefixLayout != null) {
144 paintBackground(g, myView.getPrefixAttributes(), 0, 0, prefixLayout.getWidth());
147 VisualLinesIterator visLinesIterator = new VisualLinesIterator(myView, startVisualLine);
148 while (!visLinesIterator.atEnd()) {
149 int visualLine = visLinesIterator.getVisualLine();
150 if (visualLine > endVisualLine || visualLine >= lineCount) break;
151 int y = myView.visualLineToY(visualLine);
152 paintLineFragments(g, clip, visLinesIterator, y, new LineFragmentPainter() {
154 public void paintBeforeLineStart(Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) {
155 paintBackground(g, attributes, 0, y, xEnd);
156 paintSelectionOnSecondSoftWrapLineIfNecessary(g, columnEnd, xEnd, y, primarySelectionStart, primarySelectionEnd);
160 public void paint(Graphics2D g, VisualLineFragmentsIterator.Fragment fragment, int start, int end,
161 TextAttributes attributes, float xStart, float xEnd, int y) {
162 paintBackground(g, attributes, xStart, y, xEnd - xStart);
166 public void paintAfterLineEnd(Graphics2D g, Rectangle clip, IterationState it, int columnStart, float x, int y) {
167 paintBackground(g, it.getPastLineEndBackgroundAttributes(), x, y, clip.x + clip.width - x);
168 int offset = it.getEndOffset();
169 SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset);
170 if (softWrap == null) {
171 paintVirtualSelectionIfNecessary(g, virtualSelectionMap, columnStart, x, clip.x + clip.width, y);
174 paintSelectionOnFirstSoftWrapLineIfNecessary(g, columnStart, x, clip.x + clip.width, y,
175 primarySelectionStart, primarySelectionEnd);
179 visLinesIterator.advance();
183 private Map<Integer, Couple<Integer>> createVirtualSelectionMap(int startVisualLine, int endVisualLine) {
184 HashMap<Integer, Couple<Integer>> map = new HashMap<Integer, Couple<Integer>>();
185 for (Caret caret : myEditor.getCaretModel().getAllCarets()) {
186 if (caret.hasSelection()) {
187 VisualPosition selectionStart = caret.getSelectionStartPosition();
188 VisualPosition selectionEnd = caret.getSelectionEndPosition();
189 if (selectionStart.line == selectionEnd.line) {
190 int line = selectionStart.line;
191 if (line >= startVisualLine && line <= endVisualLine) {
192 map.put(line, Couple.of(selectionStart.column, selectionEnd.column));
200 private void paintVirtualSelectionIfNecessary(Graphics2D g,
201 Map<Integer, Couple<Integer>> virtualSelectionMap,
206 int visualLine = myView.yToVisualLine(y);
207 Couple<Integer> selectionRange = virtualSelectionMap.get(visualLine);
208 if (selectionRange == null || selectionRange.second <= columnStart) return;
209 float startX = selectionRange.first <= columnStart ? xStart :
210 myView.visualPositionToXY(new VisualPosition(visualLine, selectionRange.first)).x;
211 float endX = Math.min(xEnd, myView.visualPositionToXY(new VisualPosition(visualLine, selectionRange.second)).x);
212 paintBackground(g, myEditor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), startX, y, endX - startX);
215 private void paintSelectionOnSecondSoftWrapLineIfNecessary(Graphics2D g, int columnEnd, float xEnd, int y,
216 VisualPosition selectionStartPosition, VisualPosition selectionEndPosition) {
217 int visualLine = myView.yToVisualLine(y);
219 if (selectionStartPosition.equals(selectionEndPosition) ||
220 visualLine < selectionStartPosition.line ||
221 visualLine > selectionEndPosition.line ||
222 visualLine == selectionStartPosition.line && selectionStartPosition.column >= columnEnd) {
226 float startX = (selectionStartPosition.line == visualLine && selectionStartPosition.column > 0) ?
227 myView.visualPositionToXY(selectionStartPosition).x : 0;
228 float endX = (selectionEndPosition.line == visualLine && selectionEndPosition.column < columnEnd) ?
229 myView.visualPositionToXY(selectionEndPosition).x : xEnd;
231 paintBackground(g, myEditor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), startX, y, endX - startX);
234 private void paintSelectionOnFirstSoftWrapLineIfNecessary(Graphics2D g, int columnStart, float xStart, float xEnd, int y,
235 VisualPosition selectionStartPosition, VisualPosition selectionEndPosition) {
236 int visualLine = myView.yToVisualLine(y);
238 if (selectionStartPosition.equals(selectionEndPosition) ||
239 visualLine < selectionStartPosition.line ||
240 visualLine > selectionEndPosition.line ||
241 visualLine == selectionEndPosition.line && selectionEndPosition.column <= columnStart) {
245 float startX = selectionStartPosition.line == visualLine && selectionStartPosition.column > columnStart ?
246 myView.visualPositionToXY(selectionStartPosition).x : xStart;
247 float endX = selectionEndPosition.line == visualLine ?
248 myView.visualPositionToXY(selectionEndPosition).x : xEnd;
250 paintBackground(g, myEditor.getColorsScheme().getColor(EditorColors.SELECTION_BACKGROUND_COLOR), startX, y, endX - startX);
253 private void paintBackground(Graphics2D g, TextAttributes attributes, float x, int y, float width) {
254 if (attributes == null) return;
255 paintBackground(g, attributes.getBackgroundColor(), x, y, width);
258 private void paintBackground(Graphics2D g, Color color, float x, int y, float width) {
261 color.equals(myEditor.getColorsScheme().getDefaultBackground()) ||
262 color.equals(myEditor.getBackgroundColor())) return;
264 int xStartRounded = (int)x;
265 int xEndRounded = (int)(x + width);
266 g.fillRect(xStartRounded, y, xEndRounded - xStartRounded, myView.getLineHeight());
269 private void paintCustomRenderers(final Graphics2D g, final int startOffset, final int endOffset) {
270 myEditor.getMarkupModel().processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
272 public boolean process(RangeHighlighterEx highlighter) {
273 if (highlighter.getEditorFilter().avaliableIn(myEditor)) {
274 CustomHighlighterRenderer customRenderer = highlighter.getCustomRenderer();
275 if (customRenderer != null && startOffset < highlighter.getEndOffset() && highlighter.getStartOffset() < endOffset) {
276 customRenderer.paint(myEditor, highlighter, g);
284 private void paintLineMarkersSeparators(final Graphics g,
285 final Rectangle clip,
286 MarkupModelEx markupModel,
289 markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
291 public boolean process(RangeHighlighterEx highlighter) {
292 if (highlighter.getEditorFilter().avaliableIn(myEditor)) {
293 paintLineMarkerSeparator(highlighter, clip, g);
300 private void paintLineMarkerSeparator(RangeHighlighter marker, Rectangle clip, Graphics g) {
301 Color separatorColor = marker.getLineSeparatorColor();
302 LineSeparatorRenderer lineSeparatorRenderer = marker.getLineSeparatorRenderer();
303 if (separatorColor == null && lineSeparatorRenderer == null) {
306 int line = myDocument.getLineNumber(marker.getLineSeparatorPlacement() == SeparatorPlacement.TOP
307 ? marker.getStartOffset()
308 : marker.getEndOffset());
309 int visualLine = myView.logicalToVisualPosition(new LogicalPosition(line + (marker.getLineSeparatorPlacement() ==
310 SeparatorPlacement.TOP ? 0 : 1), 0), false).line;
311 int y = myView.visualLineToY(visualLine) - 1;
312 int endShift = clip.x + clip.width;
313 EditorSettings settings = myEditor.getSettings();
314 if (settings.isRightMarginShown() && myEditor.getColorsScheme().getColor(EditorColors.RIGHT_MARGIN_COLOR) != null) {
315 endShift = Math.min(endShift, settings.getRightMargin(myEditor.getProject()) * myView.getPlainSpaceWidth());
318 g.setColor(separatorColor);
319 if (lineSeparatorRenderer != null) {
320 lineSeparatorRenderer.drawLine(g, 0, endShift, y);
323 UIUtil.drawLine(g, 0, y, endShift, y);
328 private void paintTextWithEffects(Graphics2D g, Rectangle clip, int startVisualLine, int endVisualLine) {
329 final CharSequence text = myDocument.getImmutableCharSequence();
330 final EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy = myEditor.new LineWhitespacePaintingStrategy();
331 boolean paintAllSoftWraps = myEditor.getSettings().isAllSoftWrapsShown();
332 int lineCount = myEditor.getVisibleLineCount();
334 LineLayout prefixLayout = myView.getPrefixLayout();
335 if (startVisualLine == 0 && prefixLayout != null) {
336 g.setColor(myView.getPrefixAttributes().getForegroundColor());
337 paintLineLayoutWithEffect(g, prefixLayout, 0, myView.getAscent(),
338 myView.getPrefixAttributes().getEffectColor(), myView.getPrefixAttributes().getEffectType());
341 VisualLinesIterator visLinesIterator = new VisualLinesIterator(myView, startVisualLine);
342 while (!visLinesIterator.atEnd()) {
343 int visualLine = visLinesIterator.getVisualLine();
344 if (visualLine > endVisualLine || visualLine >= lineCount) break;
346 int y = myView.visualLineToY(visualLine) + myView.getAscent();
347 final boolean paintSoftWraps = paintAllSoftWraps ||
348 myEditor.getCaretModel().getLogicalPosition().line == visLinesIterator.getStartLogicalLine();
349 final int[] currentLogicalLine = new int[] {-1};
351 paintLineFragments(g, clip, visLinesIterator, y, new LineFragmentPainter() {
353 public void paintBeforeLineStart(Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y) {
354 if (paintSoftWraps) {
355 SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel();
356 int symbolWidth = softWrapModel.getMinDrawingWidthInPixels(SoftWrapDrawingType.AFTER_SOFT_WRAP);
357 softWrapModel.doPaint(g, SoftWrapDrawingType.AFTER_SOFT_WRAP,
358 (int)xEnd - symbolWidth, y - myView.getAscent(), myView.getLineHeight());
363 public void paint(Graphics2D g, VisualLineFragmentsIterator.Fragment fragment, int start, int end,
364 TextAttributes attributes, float xStart, float xEnd, int y) {
365 if (attributes != null && attributes.getForegroundColor() != null) {
366 g.setColor(attributes.getForegroundColor());
367 fragment.draw(g, xStart, y, start, end);
369 if (fragment.getCurrentFoldRegion() == null) {
370 int logicalLine = fragment.getStartLogicalLine();
371 if (logicalLine != currentLogicalLine[0]) {
372 whitespacePaintingStrategy.update(text, myDocument.getLineStartOffset(logicalLine), myDocument.getLineEndOffset(logicalLine));
373 currentLogicalLine[0] = logicalLine;
375 paintWhitespace(g, text, xStart, y, start, end, whitespacePaintingStrategy, fragment);
377 boolean allowBorder = fragment.getCurrentFoldRegion() != null;
378 if (attributes != null && hasTextEffect(attributes.getEffectColor(), attributes.getEffectType(), allowBorder)) {
379 paintTextEffect(g, xStart, xEnd, y, attributes.getEffectColor(), attributes.getEffectType(), allowBorder);
384 public void paintAfterLineEnd(Graphics2D g, Rectangle clip, IterationState iterationState, int columnStart, float x, int y) {
385 int offset = iterationState.getEndOffset();
386 SoftWrapModelImpl softWrapModel = myEditor.getSoftWrapModel();
387 if (softWrapModel.getSoftWrap(offset) == null) {
388 int logicalLine = myDocument.getLineNumber(offset);
389 paintLineExtensions(g, logicalLine, x, y);
391 else if (paintSoftWraps) {
392 softWrapModel.doPaint(g, SoftWrapDrawingType.BEFORE_SOFT_WRAP_LINE_FEED,
393 (int)x, y - myView.getAscent(), myView.getLineHeight());
397 visLinesIterator.advance();
401 private float paintLineLayoutWithEffect(Graphics2D g, LineLayout layout, float x, float y,
402 @Nullable Color effectColor, @Nullable EffectType effectType) {
404 for (LineLayout.VisualFragment fragment : layout.getFragmentsInVisualOrder(x)) {
405 fragment.draw(g, x, y);
406 x = fragment.getEndX();
409 if (hasTextEffect(effectColor, effectType, false)) {
410 paintTextEffect(g, initialX, x, (int)y, effectColor, effectType, false);
415 private static boolean hasTextEffect(@Nullable Color effectColor, @Nullable EffectType effectType, boolean allowBorder) {
416 return effectColor != null && (effectType == EffectType.LINE_UNDERSCORE ||
417 effectType == EffectType.BOLD_LINE_UNDERSCORE ||
418 effectType == EffectType.BOLD_DOTTED_LINE ||
419 effectType == EffectType.WAVE_UNDERSCORE ||
420 effectType == EffectType.STRIKEOUT ||
421 allowBorder && (effectType == EffectType.BOXED || effectType == EffectType.ROUNDED_BOX));
424 private void paintTextEffect(Graphics2D g, float xFrom, float xTo, int y, Color effectColor, EffectType effectType, boolean allowBorder) {
425 int xStart = (int)xFrom;
427 g.setColor(effectColor);
428 if (effectType == EffectType.LINE_UNDERSCORE) {
429 UIUtil.drawLine(g, xStart, y + 1, xEnd, y + 1);
431 else if (effectType == EffectType.BOLD_LINE_UNDERSCORE) {
432 int height = JBUI.scale(Registry.intValue("editor.bold.underline.height", 2));
433 g.fillRect(xStart, y, xEnd - xStart, height);
435 else if (effectType == EffectType.STRIKEOUT) {
436 int y1 = y - myView.getCharHeight() / 2;
437 UIUtil.drawLine(g, xStart, y1, xEnd, y1);
439 else if (effectType == EffectType.WAVE_UNDERSCORE) {
440 UIUtil.drawWave(g, new Rectangle(xStart, y + 1, xEnd - xStart, myView.getDescent() - 1));
442 else if (effectType == EffectType.BOLD_DOTTED_LINE) {
443 UIUtil.drawBoldDottedLine(g, xStart, xEnd, SystemInfo.isMac ? y : y + 1, myEditor.getBackgroundColor(), g.getColor(), false);
445 else if (allowBorder && (effectType == EffectType.BOXED || effectType == EffectType.ROUNDED_BOX)) {
446 drawSimpleBorder(g, xStart, xEnd - 1, y - myView.getAscent(), effectType == EffectType.ROUNDED_BOX);
450 private void paintWhitespace(Graphics2D g, CharSequence text, float x, int y, int start, int end,
451 EditorImpl.LineWhitespacePaintingStrategy whitespacePaintingStrategy,
452 VisualLineFragmentsIterator.Fragment fragment) {
453 g.setColor(myEditor.getColorsScheme().getColor(EditorColors.WHITESPACES_COLOR));
454 boolean isRtl = fragment.isRtl();
455 int baseStartOffset = fragment.getStartOffset();
456 int startOffset = isRtl ? baseStartOffset - start : baseStartOffset + start;
457 for (int i = start; i < end; i++) {
458 int charOffset = isRtl ? baseStartOffset - i - 1 : baseStartOffset + i;
459 char c = text.charAt(charOffset);
460 if (" \t\u3000".indexOf(c) >= 0 && whitespacePaintingStrategy.showWhitespaceAtOffset(charOffset)) {
461 int startX = (int)fragment.offsetToX(x, startOffset, isRtl ? baseStartOffset - i : baseStartOffset + i);
462 int endX = (int)fragment.offsetToX(x, startOffset, isRtl ? baseStartOffset - i - 1 : baseStartOffset + i + 1);
465 g.fillRect((startX + endX) / 2, y, 1, 1);
467 else if (c == '\t') {
468 endX -= myView.getPlainSpaceWidth() / 4;
469 int height = myView.getCharHeight();
470 int halfHeight = height / 2;
471 int mid = y - halfHeight;
472 int top = y - height;
473 UIUtil.drawLine(g, startX, mid, endX, mid);
474 UIUtil.drawLine(g, endX, y, endX, top);
475 g.fillPolygon(new int[]{endX - halfHeight, endX - halfHeight, endX}, new int[]{y, y - height, y - halfHeight}, 3);
477 else if (c == '\u3000') { // ideographic space
478 final int charHeight = myView.getCharHeight();
479 g.drawRect(startX + 2, y - charHeight, endX - startX - 4, charHeight);
485 private void paintLineExtensions(Graphics2D g, int line, float x, int y) {
486 Project project = myEditor.getProject();
487 VirtualFile virtualFile = myEditor.getVirtualFile();
488 if (project == null || virtualFile == null) return;
489 for (EditorLinePainter painter : EditorLinePainter.EP_NAME.getExtensions()) {
490 Collection<LineExtensionInfo> extensions = painter.getLineExtensions(project, virtualFile, line);
491 if (extensions != null) {
492 for (LineExtensionInfo info : extensions) {
493 LineLayout layout = LineLayout.create(myView, info.getText(), info.getFontType());
494 g.setColor(info.getColor());
495 x = paintLineLayoutWithEffect(g, layout, x, y, info.getEffectColor(), info.getEffectType());
496 int currentLineWidth = (int)x;
497 EditorSizeManager sizeManager = myView.getSizeManager();
498 if (currentLineWidth > sizeManager.getMaxLineWithExtensionWidth()) {
499 sizeManager.setMaxLineWithExtensionWidth(line, currentLineWidth);
506 private void paintHighlightersAfterEndOfLine(final Graphics2D g,
507 MarkupModelEx markupModel,
508 final int startOffset,
510 markupModel.processRangeHighlightersOverlappingWith(startOffset, endOffset, new Processor<RangeHighlighterEx>() {
512 public boolean process(RangeHighlighterEx highlighter) {
513 if (highlighter.getEditorFilter().avaliableIn(myEditor) && highlighter.getStartOffset() >= startOffset) {
514 paintHighlighterAfterEndOfLine(g, highlighter);
521 private void paintHighlighterAfterEndOfLine(Graphics2D g, RangeHighlighterEx highlighter) {
522 if (!highlighter.isAfterEndOfLine()) {
525 int startOffset = highlighter.getStartOffset();
526 int lineEndOffset = myDocument.getLineEndOffset(myDocument.getLineNumber(startOffset));
527 if (myEditor.getFoldingModel().isOffsetCollapsed(lineEndOffset)) return;
528 Point lineEnd = myView.offsetToXY(lineEndOffset, true, false);
531 TextAttributes attributes = highlighter.getTextAttributes();
532 paintBackground(g, attributes, x, y, myView.getPlainSpaceWidth());
533 if (attributes != null && hasTextEffect(attributes.getEffectColor(), attributes.getEffectType(), false)) {
534 paintTextEffect(g, x, x + myView.getPlainSpaceWidth() - 1, y + myView.getAscent(),
535 attributes.getEffectColor(), attributes.getEffectType(), false);
539 private void paintBorderEffect(Graphics2D g,
540 ClipDetector clipDetector,
541 EditorHighlighter highlighter,
544 HighlighterIterator it = highlighter.createIterator(clipStartOffset);
545 while (!it.atEnd() && it.getStart() < clipEndOffset) {
546 TextAttributes attributes = it.getTextAttributes();
547 if (isBorder(attributes)) {
548 paintBorderEffect(g, clipDetector, it.getStart(), it.getEnd(), attributes);
554 private void paintBorderEffect(final Graphics2D g,
555 final ClipDetector clipDetector,
556 MarkupModelEx markupModel,
559 markupModel.processRangeHighlightersOverlappingWith(clipStartOffset, clipEndOffset, new Processor<RangeHighlighterEx>() {
561 public boolean process(RangeHighlighterEx rangeHighlighter) {
562 if (rangeHighlighter.getEditorFilter().avaliableIn(myEditor)) {
563 TextAttributes attributes = rangeHighlighter.getTextAttributes();
564 if (isBorder(attributes)) {
565 paintBorderEffect(g, clipDetector, rangeHighlighter.getAffectedAreaStartOffset(), rangeHighlighter.getAffectedAreaEndOffset(),
574 private static boolean isBorder(TextAttributes attributes) {
575 return attributes != null &&
576 (attributes.getEffectType() == EffectType.BOXED || attributes.getEffectType() == EffectType.ROUNDED_BOX) &&
577 attributes.getEffectColor() != null;
580 private void paintBorderEffect(Graphics2D g, ClipDetector clipDetector, int startOffset, int endOffset, TextAttributes attributes) {
581 if (!clipDetector.rangeCanBeVisible(startOffset, endOffset)) return;
582 int startLine = myDocument.getLineNumber(startOffset);
583 int endLine = myDocument.getLineNumber(endOffset);
584 if (startLine + 1 == endLine &&
585 startOffset == myDocument.getLineStartOffset(startLine) &&
586 endOffset == myDocument.getLineStartOffset(endLine)) {
587 // special case of line highlighters
589 endOffset = myDocument.getLineEndOffset(endLine);
592 boolean rounded = attributes.getEffectType() == EffectType.ROUNDED_BOX;
593 g.setColor(attributes.getEffectColor());
594 VisualPosition startPosition = myView.offsetToVisualPosition(startOffset, true, false);
595 VisualPosition endPosition = myView.offsetToVisualPosition(endOffset, false, true);
596 if (startPosition.line == endPosition.line) {
597 int y = myView.visualLineToY(startPosition.line);
598 TFloatArrayList ranges = adjustedLogicalRangeToVisualRanges(startOffset, endOffset);
599 for (int i = 0; i < ranges.size() - 1; i+= 2) {
600 int startX = (int)ranges.get(i);
601 int endX = (int)ranges.get(i + 1);
602 drawSimpleBorder(g, startX, endX, y, rounded);
606 TFloatArrayList leadingRanges = adjustedLogicalRangeToVisualRanges(
607 startOffset, myView.visualPositionToOffset(new VisualPosition(startPosition.line, Integer.MAX_VALUE, true)));
608 TFloatArrayList trailingRanges = adjustedLogicalRangeToVisualRanges(
609 myView.visualPositionToOffset(new VisualPosition(endPosition.line, 0)), endOffset);
610 if (!leadingRanges.isEmpty() && !trailingRanges.isEmpty()) {
611 int maxWidth = Math.max(myView.getMaxWidthInLineRange(startPosition.line, endPosition.line - 1) - 1,
612 (int)trailingRanges.get(trailingRanges.size() - 1));
613 boolean containsInnerLines = endPosition.line > startPosition.line + 1;
614 int lineHeight = myView.getLineHeight() - 1;
615 int leadingTopY = myView.visualLineToY(startPosition.line);
616 int leadingBottomY = leadingTopY + lineHeight;
617 int trailingTopY = myView.visualLineToY(endPosition.line);
618 int trailingBottomY = trailingTopY + lineHeight;
621 float leftGap = leadingRanges.get(0) - (containsInnerLines ? 0 : trailingRanges.get(0));
622 int adjustY = leftGap == 0 ? 2 : leftGap > 0 ? 1 : 0; // avoiding 1-pixel gap between aligned lines
623 for (int i = 0; i < leadingRanges.size() - 1; i += 2) {
624 start = leadingRanges.get(i);
625 end = leadingRanges.get(i + 1);
627 drawLine(g, leadingRanges.get(i - 1), leadingBottomY, start, leadingBottomY, rounded);
629 drawLine(g, start, leadingBottomY + (i == 0 ? adjustY : 0), start, leadingTopY, rounded);
630 if ((i + 2) < leadingRanges.size()) {
631 drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
632 drawLine(g, end, leadingTopY, end, leadingBottomY, rounded);
635 end = Math.max(end, maxWidth);
636 drawLine(g, start, leadingTopY, end, leadingTopY, rounded);
637 drawLine(g, end, leadingTopY, end, trailingTopY - 1, rounded);
638 float targetX = trailingRanges.get(trailingRanges.size() - 1);
639 drawLine(g, end, trailingTopY - 1, targetX, trailingTopY - 1, rounded);
640 adjustY = end == targetX ? -2 : -1; // for lastX == targetX we need to avoid a gap when rounding is used
641 for (int i = trailingRanges.size() - 2; i >= 0; i -= 2) {
642 start = trailingRanges.get(i);
643 end = trailingRanges.get(i + 1);
645 drawLine(g, end, trailingTopY + (i == 0 ? adjustY : 0), end, trailingBottomY, rounded);
646 drawLine(g, end, trailingBottomY, start, trailingBottomY, rounded);
647 drawLine(g, start, trailingBottomY, start, trailingTopY, rounded);
649 drawLine(g, start, trailingTopY, trailingRanges.get(i - 1), trailingTopY, rounded);
653 if (containsInnerLines) {
655 drawLine(g, start, trailingTopY, start, trailingTopY - 1, rounded);
656 drawLine(g, start, trailingTopY - 1, 0, trailingTopY - 1, rounded);
657 drawLine(g, 0, trailingTopY - 1, 0, leadingBottomY + 1, rounded);
660 drawLine(g, start, trailingTopY, 0, leadingBottomY + 1, rounded);
664 targetX = leadingRanges.get(0);
665 if (lastX < targetX) {
666 drawLine(g, lastX, leadingBottomY + 1, targetX, leadingBottomY + 1, rounded);
669 drawLine(g, lastX, leadingBottomY + 1, lastX, leadingBottomY, rounded);
670 drawLine(g, lastX, leadingBottomY, targetX, leadingBottomY, rounded);
676 private void drawSimpleBorder(Graphics2D g, int xStart, int xEnd, int y, boolean rounded) {
677 int height = myView.getLineHeight() - 1;
679 UIUtil.drawRectPickedOut(g, xStart, y, xEnd - xStart, height);
682 g.drawRect(xStart, y, xEnd - xStart, height);
686 private static void drawLine(Graphics2D g, float x1, int y1, float x2, int y2, boolean rounded) {
688 UIUtil.drawLinePickedOut(g, (int) x1, y1, (int)x2, y2);
690 UIUtil.drawLine(g, (int)x1, y1, (int)x2, y2);
695 * Returns ranges obtained from {@link #logicalRangeToVisualRanges(int, int)}, adjusted for painting range border - lines should
696 * line inside target ranges (except for empty range). Target offsets are supposed to be located on the same visual line.
698 private TFloatArrayList adjustedLogicalRangeToVisualRanges(int startOffset, int endOffset) {
699 TFloatArrayList ranges = logicalRangeToVisualRanges(startOffset, endOffset);
700 for (int i = 0; i < ranges.size() - 1; i += 2) {
701 float startX = ranges.get(i);
702 float endX = ranges.get(i + 1);
703 if (startX == endX) {
709 ranges.set(i + 1, endX);
716 * Returns a list of pairs of x coordinates for visual ranges representing given logical range. If
717 * <code>startOffset == endOffset</code>, a pair of equal numbers is returned, corresponding to target position. Target offsets are
718 * supposed to be located on the same visual line.
720 private TFloatArrayList logicalRangeToVisualRanges(int startOffset, int endOffset) {
721 assert startOffset <= endOffset;
722 TFloatArrayList result = new TFloatArrayList();
723 if (myDocument.getTextLength() == 0) {
728 for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, startOffset, false)) {
729 int minOffset = fragment.getMinOffset();
730 int maxOffset = fragment.getMaxOffset();
731 if (startOffset == endOffset) {
732 if (startOffset >= minOffset && startOffset <= maxOffset) {
733 float x = fragment.offsetToX(startOffset);
739 else if (startOffset < maxOffset && endOffset > minOffset) {
740 float x1 = fragment.offsetToX(Math.max(minOffset, startOffset));
741 float x2 = fragment.offsetToX(Math.min(maxOffset, endOffset));
747 if (result.isEmpty() || x1 > result.get(result.size() - 1)) {
752 result.set(result.size() - 1, x2);
760 private void paintComposedTextDecoration(Graphics2D g) {
761 TextRange composedTextRange = myEditor.getComposedTextRange();
762 if (composedTextRange != null) {
763 Point p1 = myView.offsetToXY(Math.min(composedTextRange.getStartOffset(), myDocument.getTextLength()), true, false);
764 Point p2 = myView.offsetToXY(Math.min(composedTextRange.getEndOffset(), myDocument.getTextLength()), false, true);
766 int y = p1.y + myView.getAscent() + 1;
768 g.setStroke(IME_COMPOSED_TEXT_UNDERLINE_STROKE);
769 g.setColor(myEditor.getColorsScheme().getDefaultForeground());
770 UIUtil.drawLine(g, p1.x, y, p2.x, y);
774 private void paintCaret(Graphics2D g_) {
775 EditorImpl.CaretRectangle[] locations = myEditor.getCaretLocations(true);
776 if (locations == null) return;
778 Graphics2D g = IdeBackgroundUtil.getOriginalGraphics(g_);
779 int lineHeight = myView.getLineHeight();
780 EditorSettings settings = myEditor.getSettings();
781 Color caretColor = myEditor.getColorsScheme().getColor(EditorColors.CARET_COLOR);
782 if (caretColor == null) caretColor = new JBColor(CARET_DARK, CARET_LIGHT);
783 g.setColor(caretColor);
784 for (EditorImpl.CaretRectangle location : locations) {
785 int x = location.myPoint.x;
786 int y = location.myPoint.y;
787 Caret caret = location.myCaret;
788 boolean isRtl = location.myIsRtl;
789 if (myEditor.isInsertMode() != settings.isBlockCursor()) {
790 int lineWidth = JBUI.scale(settings.getLineCursorWidth());
791 if (x > 0 && lineWidth > 1) x--; // fully cover extra character's pixel which can appear due to antialiasing
792 g.fillRect(x, y, lineWidth, lineHeight);
793 if (myDocument.getTextLength() > 0 && caret != null &&
794 !myView.getTextLayoutCache().getLineLayout(caret.getLogicalPosition().line).isLtr()) {
795 g.fillPolygon(new int[]{
796 isRtl ? x + lineWidth : x,
797 isRtl ? x + lineWidth - CARET_DIRECTION_MARK_SIZE : x + CARET_DIRECTION_MARK_SIZE,
798 isRtl ? x + lineWidth : x
800 new int[]{y, y, y + CARET_DIRECTION_MARK_SIZE}, 3);
804 int width = location.myWidth;
805 int startX = Math.max(0, isRtl ? x - width : x);
806 g.fillRect(startX, y, width, lineHeight - 1);
807 if (myDocument.getTextLength() > 0 && caret != null) {
808 int targetVisualColumn = caret.getVisualPosition().column;
809 for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView,
810 caret.getVisualLineStart(),
812 int startVisualColumn = fragment.getStartVisualColumn();
813 int endVisualColumn = fragment.getEndVisualColumn();
814 if (startVisualColumn < targetVisualColumn && endVisualColumn > targetVisualColumn ||
815 startVisualColumn == targetVisualColumn && !isRtl ||
816 endVisualColumn == targetVisualColumn && isRtl) {
817 g.setColor(ColorUtil.isDark(caretColor) ? CARET_LIGHT : CARET_DARK);
818 fragment.draw(g, startX, y + myView.getAscent(),
819 targetVisualColumn - startVisualColumn - (isRtl ? 1 : 0),
820 targetVisualColumn - startVisualColumn + (isRtl ? 0 : 1));
829 void repaintCarets() {
830 EditorImpl.CaretRectangle[] locations = myEditor.getCaretLocations(false);
831 if (locations == null) return;
832 int lineHeight = myView.getLineHeight();
833 for (EditorImpl.CaretRectangle location : locations) {
834 int x = location.myPoint.x;
835 int y = location.myPoint.y;
836 int width = Math.max(location.myWidth, CARET_DIRECTION_MARK_SIZE);
837 myEditor.getContentComponent().repaintEditorComponent(x - width, y, width * 2, lineHeight);
841 private void paintLineFragments(Graphics2D g, Rectangle clip, VisualLinesIterator visLineIterator, int y, LineFragmentPainter painter) {
842 int visualLine = visLineIterator.getVisualLine();
843 float x = visualLine == 0 ? myView.getPrefixTextWidthInPixels() : 0;
844 int offset = visLineIterator.getVisualLineStartOffset();
845 int visualLineEndOffset = visLineIterator.getVisualLineEndOffset();
846 IterationState it = null;
847 int prevEndOffset = -1;
848 boolean firstFragment = true;
850 for (VisualLineFragmentsIterator.Fragment fragment : VisualLineFragmentsIterator.create(myView, visLineIterator, null)) {
851 int fragmentStartOffset = fragment.getStartOffset();
852 int start = fragmentStartOffset;
853 int end = fragment.getEndOffset();
854 x = fragment.getStartX();
856 firstFragment = false;
857 SoftWrap softWrap = myEditor.getSoftWrapModel().getSoftWrap(offset);
858 if (softWrap != null) {
859 prevEndOffset = offset;
860 it = new IterationState(myEditor, offset == 0 ? 0 : offset - 1, visualLineEndOffset, true, false, false, false);
861 if (it.getEndOffset() <= offset) {
864 if (x >= clip.getMinX()) {
865 painter.paintBeforeLineStart(g, it.getStartOffset() == offset ? it.getBeforeLineStartBackgroundAttributes() :
866 it.getMergedAttributes(), fragment.getStartVisualColumn(), x, y);
870 FoldRegion foldRegion = fragment.getCurrentFoldRegion();
871 if (foldRegion == null) {
872 if (start != prevEndOffset) {
873 it = new IterationState(myEditor, start, fragment.isRtl() ? offset : visualLineEndOffset, true, false, false, fragment.isRtl());
877 while (fragment.isRtl() ? start > end : start < end) {
878 if (fragment.isRtl() ? it.getEndOffset() >= start : it.getEndOffset() <= start) {
882 TextAttributes attributes = it.getMergedAttributes();
883 int curEnd = fragment.isRtl() ? Math.max(it.getEndOffset(), end) : Math.min(it.getEndOffset(), end);
884 float xNew = fragment.offsetToX(x, start, curEnd);
885 if (xNew >= clip.getMinX()) {
886 painter.paint(g, fragment,
887 fragment.isRtl() ? fragmentStartOffset - start : start - fragmentStartOffset,
888 fragment.isRtl() ? fragmentStartOffset - curEnd : curEnd - fragmentStartOffset,
889 attributes, x, xNew, y);
896 float xNew = fragment.getEndX();
897 if (xNew >= clip.getMinX()) {
898 painter.paint(g, fragment, 0, fragment.getEndVisualColumn() - fragment.getStartVisualColumn(), getFoldRegionAttributes(foldRegion),
905 if (x > clip.getMaxX()) return;
906 maxColumn = fragment.getEndVisualColumn();
908 if (it == null || it.getEndOffset() != visualLineEndOffset) {
909 it = new IterationState(myEditor, visualLineEndOffset == offset ? visualLineEndOffset : visualLineEndOffset - 1, visualLineEndOffset,
910 true, false, false, false);
916 painter.paintAfterLineEnd(g, clip, it, maxColumn, x, y);
919 private TextAttributes getFoldRegionAttributes(FoldRegion foldRegion) {
920 TextAttributes selectionAttributes = isSelected(foldRegion) ? myEditor.getSelectionModel().getTextAttributes() : null;
921 TextAttributes foldAttributes = myEditor.getFoldingModel().getPlaceholderAttributes();
922 TextAttributes defaultAttributes = getDefaultAttributes();
923 return mergeAttributes(mergeAttributes(selectionAttributes, foldAttributes), defaultAttributes);
926 @SuppressWarnings("UseJBColor")
927 private TextAttributes getDefaultAttributes() {
928 TextAttributes attributes = myEditor.getColorsScheme().getAttributes(HighlighterColors.TEXT);
929 if (attributes.getForegroundColor() == null) attributes.setForegroundColor(Color.black);
930 if (attributes.getBackgroundColor() == null) attributes.setBackgroundColor(Color.white);
934 private static boolean isSelected(FoldRegion foldRegion) {
935 int regionStart = foldRegion.getStartOffset();
936 int regionEnd = foldRegion.getEndOffset();
937 int[] selectionStarts = foldRegion.getEditor().getSelectionModel().getBlockSelectionStarts();
938 int[] selectionEnds = foldRegion.getEditor().getSelectionModel().getBlockSelectionEnds();
939 for (int i = 0; i < selectionStarts.length; i++) {
940 int start = selectionStarts[i];
941 int end = selectionEnds[i];
942 if (regionStart >= start && regionEnd <= end) return true;
947 private static TextAttributes mergeAttributes(TextAttributes primary, TextAttributes secondary) {
948 if (primary == null) return secondary;
949 if (secondary == null) return primary;
950 return new TextAttributes(primary.getForegroundColor() == null ? secondary.getForegroundColor() : primary.getForegroundColor(),
951 primary.getBackgroundColor() == null ? secondary.getBackgroundColor() : primary.getBackgroundColor(),
952 primary.getEffectColor() == null ? secondary.getEffectColor() : primary.getEffectColor(),
953 primary.getEffectType() == null ? secondary.getEffectType() : primary.getEffectType(),
954 primary.getFontType() == Font.PLAIN ? secondary.getFontType() : primary.getFontType());
958 public void drawChars(@NotNull Graphics g, @NotNull char[] data, int start, int end, int x, int y, Color color, FontInfo fontInfo) {
959 g.setFont(fontInfo.getFont());
961 g.drawChars(data, start, end - start, x, y);
964 interface LineFragmentPainter {
965 void paintBeforeLineStart(Graphics2D g, TextAttributes attributes, int columnEnd, float xEnd, int y);
966 void paint(Graphics2D g, VisualLineFragmentsIterator.Fragment fragment, int start, int end, TextAttributes attributes,
967 float xStart, float xEnd, int y);
968 void paintAfterLineEnd(Graphics2D g, Rectangle clip, IterationState iterationState, int columnStart, float x, int y);