2 * Copyright 2000-2014 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.richcopy;
18 import com.intellij.codeInsight.daemon.impl.HighlightInfo;
19 import com.intellij.codeInsight.daemon.impl.HighlightInfoType;
20 import com.intellij.codeInsight.editorActions.CopyPastePostProcessor;
21 import com.intellij.codeInsight.editorActions.CopyPastePreProcessor;
22 import com.intellij.ide.highlighter.HighlighterFactory;
23 import com.intellij.openapi.application.ApplicationManager;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.editor.*;
26 import com.intellij.openapi.editor.colors.EditorColorsScheme;
27 import com.intellij.openapi.editor.colors.FontPreferences;
28 import com.intellij.openapi.editor.colors.TextAttributesKey;
29 import com.intellij.openapi.editor.ex.DisposableIterator;
30 import com.intellij.openapi.editor.ex.MarkupModelEx;
31 import com.intellij.openapi.editor.ex.RangeHighlighterEx;
32 import com.intellij.openapi.editor.ex.util.EditorUtil;
33 import com.intellij.openapi.editor.highlighter.EditorHighlighter;
34 import com.intellij.openapi.editor.highlighter.HighlighterIterator;
35 import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry;
36 import com.intellij.openapi.editor.impl.DocumentMarkupModel;
37 import com.intellij.openapi.editor.impl.FontInfo;
38 import com.intellij.openapi.editor.markup.HighlighterLayer;
39 import com.intellij.openapi.editor.markup.MarkupModel;
40 import com.intellij.openapi.editor.markup.TextAttributes;
41 import com.intellij.openapi.editor.richcopy.model.SyntaxInfo;
42 import com.intellij.openapi.editor.richcopy.settings.RichCopySettings;
43 import com.intellij.openapi.editor.richcopy.view.HtmlTransferableData;
44 import com.intellij.openapi.editor.richcopy.view.RawTextWithMarkup;
45 import com.intellij.openapi.editor.richcopy.view.RtfTransferableData;
46 import com.intellij.openapi.project.Project;
47 import com.intellij.openapi.util.Pair;
48 import com.intellij.openapi.util.Ref;
49 import com.intellij.openapi.util.SystemInfo;
50 import com.intellij.openapi.util.registry.Registry;
51 import com.intellij.psi.PsiFile;
52 import com.intellij.psi.TokenType;
53 import com.intellij.util.ObjectUtils;
54 import com.intellij.util.text.CharArrayUtil;
55 import org.jetbrains.annotations.NotNull;
56 import org.jetbrains.annotations.Nullable;
60 import java.util.List;
63 * Generates text with markup (in RTF and HTML formats) for interaction via clipboard with third-party applications.
65 * Interoperability with the following applications was tested:
66 * MS Office 2010 (Word, PowerPoint, Outlook), OpenOffice (Writer, Impress), Gmail, Mac TextEdit, Mac Mail.
68 public class TextWithMarkupProcessor extends CopyPastePostProcessor<RawTextWithMarkup> {
69 private static final Logger LOG = Logger.getInstance("#" + TextWithMarkupProcessor.class.getName());
71 private List<RawTextWithMarkup> myResult;
75 public List<RawTextWithMarkup> collectTransferableData(PsiFile file, Editor editor, int[] startOffsets, int[] endOffsets) {
76 if (!RichCopySettings.getInstance().isEnabled()) {
77 return Collections.emptyList();
81 RichCopySettings settings = RichCopySettings.getInstance();
82 List<Caret> carets = editor.getCaretModel().getAllCarets();
83 Caret firstCaret = carets.get(0);
84 final int indentSymbolsToStrip;
85 final int firstLineStartOffset;
86 if (Registry.is("editor.richcopy.strip.indents") && carets.size() == 1) {
87 Pair<Integer, Integer> p = calcIndentSymbolsToStrip(editor.getDocument(), firstCaret.getSelectionStart(), firstCaret.getSelectionEnd());
88 firstLineStartOffset = p.first;
89 indentSymbolsToStrip = p.second;
92 firstLineStartOffset = firstCaret.getSelectionStart();
93 indentSymbolsToStrip = 0;
95 logInitial(editor, startOffsets, endOffsets, indentSymbolsToStrip, firstLineStartOffset);
96 CharSequence text = editor.getDocument().getCharsSequence();
97 EditorColorsScheme schemeToUse = settings.getColorsScheme(editor.getColorsScheme());
98 EditorHighlighter highlighter = HighlighterFactory.createHighlighter(file.getVirtualFile(), schemeToUse, file.getProject());
99 highlighter.setText(text);
100 MarkupModel markupModel = DocumentMarkupModel.forDocument(editor.getDocument(), file.getProject(), false);
101 Context context = new Context(text, schemeToUse, indentSymbolsToStrip);
103 Caret prevCaret = null;
105 for (Caret caret : carets) {
106 int caretSelectionStart = caret.getSelectionStart();
107 int caretSelectionEnd = caret.getSelectionEnd();
108 int startOffsetToUse;
109 int additionalShift = 0;
110 if (caret == firstCaret) {
111 startOffsetToUse = firstLineStartOffset;
114 startOffsetToUse = caretSelectionStart;
115 assert prevCaret != null;
116 String prevCaretSelectedText = prevCaret.getSelectedText();
117 // Block selection fills short lines by white spaces
118 int fillStringLength = prevCaretSelectedText == null ? 0 : prevCaretSelectedText.length() - (prevCaret.getSelectionEnd() - prevCaret.getSelectionStart());
119 context.addCharacter(endOffset + fillStringLength);
120 additionalShift = fillStringLength + 1;
122 context.reset(endOffset - caretSelectionStart + additionalShift);
123 endOffset = caretSelectionEnd;
125 if (endOffset <= startOffsetToUse) {
128 MarkupIterator markupIterator = new MarkupIterator(text,
129 new CompositeRangeIterator(schemeToUse,
130 new HighlighterRangeIterator(highlighter, startOffsetToUse, endOffset),
131 new MarkupModelRangeIterator(markupModel, schemeToUse, startOffsetToUse, endOffset)),
134 context.iterate(markupIterator, endOffset);
137 markupIterator.dispose();
140 SyntaxInfo syntaxInfo = context.finish();
141 logSyntaxInfo(syntaxInfo);
143 createResult(syntaxInfo, editor);
144 return ObjectUtils.notNull(myResult, Collections.<RawTextWithMarkup>emptyList());
146 catch (Exception e) {
147 // catching the exception so that the rest of copy/paste functionality can still work fine
150 return Collections.emptyList();
154 public void processTransferableData(Project project,
158 Ref<Boolean> indented,
159 List<RawTextWithMarkup> values) {
163 void createResult(SyntaxInfo syntaxInfo, Editor editor) {
164 myResult = new ArrayList<RawTextWithMarkup>(2);
165 myResult.add(new HtmlTransferableData(syntaxInfo, EditorUtil.getTabSize(editor)));
166 myResult.add(new RtfTransferableData(syntaxInfo));
169 private void setRawText(String rawText) {
170 if (myResult == null) {
173 for (RawTextWithMarkup data : myResult) {
174 data.setRawText(rawText);
179 private static void logInitial(@NotNull Editor editor,
180 @NotNull int[] startOffsets,
181 @NotNull int[] endOffsets,
182 int indentSymbolsToStrip,
183 int firstLineStartOffset)
185 if (!LOG.isDebugEnabled()) {
189 StringBuilder buffer = new StringBuilder();
190 Document document = editor.getDocument();
191 CharSequence text = document.getCharsSequence();
192 for (int i = 0; i < startOffsets.length; i++) {
193 int start = startOffsets[i];
194 int lineStart = document.getLineStartOffset(document.getLineNumber(start));
195 int end = endOffsets[i];
196 int lineEnd = document.getLineEndOffset(document.getLineNumber(end));
197 buffer.append(" region #").append(i).append(": ").append(start).append('-').append(end).append(", text at range ")
198 .append(lineStart).append('-').append(lineEnd).append(": \n'").append(text.subSequence(lineStart, lineEnd)).append("'\n");
200 if (buffer.length() > 0) {
201 buffer.setLength(buffer.length() - 1);
203 LOG.debug(String.format(
204 "Preparing syntax-aware text. Given: %s selection, indent symbols to strip=%d, first line start offset=%d, selected text:%n%s",
205 startOffsets.length > 1 ? "block" : "regular", indentSymbolsToStrip, firstLineStartOffset, buffer
209 private static void logSyntaxInfo(@NotNull SyntaxInfo info) {
210 if (LOG.isDebugEnabled()) {
211 LOG.debug("Constructed syntax info: " + info);
215 private static Pair<Integer/* start offset to use */, Integer /* indent symbols to strip */> calcIndentSymbolsToStrip(
216 @NotNull Document document, int startOffset, int endOffset)
218 int startLine = document.getLineNumber(startOffset);
219 int endLine = document.getLineNumber(endOffset);
220 CharSequence text = document.getCharsSequence();
221 int maximumCommonIndent = Integer.MAX_VALUE;
222 int firstLineStart = startOffset;
223 int firstLineEnd = startOffset;
224 for (int line = startLine; line <= endLine; line++) {
225 int lineStartOffset = document.getLineStartOffset(line);
226 int lineEndOffset = document.getLineEndOffset(line);
227 if (line == startLine) {
228 firstLineStart = lineStartOffset;
229 firstLineEnd = lineEndOffset;
231 int nonWsOffset = lineEndOffset;
232 for (int i = lineStartOffset; i < lineEndOffset && (i - lineStartOffset) < maximumCommonIndent && i < endOffset; i++) {
233 char c = text.charAt(i);
234 if (c != ' ' && c != '\t') {
239 if (nonWsOffset >= lineEndOffset) {
240 continue; // Blank line
242 int indent = nonWsOffset - lineStartOffset;
243 maximumCommonIndent = Math.min(maximumCommonIndent, indent);
244 if (maximumCommonIndent == 0) {
248 int startOffsetToUse = Math.min(firstLineEnd, Math.max(startOffset, firstLineStart + maximumCommonIndent));
249 return Pair.create(startOffsetToUse, maximumCommonIndent);
252 private static class Context {
254 private final SyntaxInfo.Builder builder;
256 @NotNull private final CharSequence myText;
257 @NotNull private final Color myDefaultForeground;
258 @NotNull private final Color myDefaultBackground;
260 @Nullable private Color myBackground;
261 @Nullable private Color myForeground;
262 @Nullable private String myFontFamilyName;
264 private final int myIndentSymbolsToStrip;
266 private int myFontStyle = -1;
267 private int myStartOffset = -1;
268 private int myOffsetShift = 0;
270 private int myIndentSymbolsToStripAtCurrentLine;
272 Context(@NotNull CharSequence charSequence, @NotNull EditorColorsScheme scheme, int indentSymbolsToStrip) {
273 myText = charSequence;
274 myDefaultForeground = scheme.getDefaultForeground();
275 myDefaultBackground = scheme.getDefaultBackground();
277 // Java assumes screen resolution of 72dpi when calculating font size in pixels. External applications are supposedly using correct
278 // resolution, so we need to adjust font size for copied text to look the same in them.
279 // (See https://docs.oracle.com/javase/7/docs/webnotes/tsg/TSG-Desktop/html/java2d.html#gdlwn)
280 // Java on Mac is not affected by this issue.
281 int javaFontSize = scheme.getEditorFontSize();
282 float fontSize = SystemInfo.isMac || ApplicationManager.getApplication().isHeadlessEnvironment() ?
284 javaFontSize * 72f / Toolkit.getDefaultToolkit().getScreenResolution();
286 builder = new SyntaxInfo.Builder(myDefaultForeground, myDefaultBackground, fontSize);
287 myIndentSymbolsToStrip = indentSymbolsToStrip;
290 public void reset(int offsetShiftDelta) {
292 myOffsetShift += offsetShiftDelta;
293 myIndentSymbolsToStripAtCurrentLine = 0;
296 public void iterate(MarkupIterator iterator, int endOffset) {
297 while (!iterator.atEnd()) {
299 int startOffset = iterator.getStartOffset();
300 if (startOffset >= endOffset) {
303 if (myStartOffset < 0) {
304 myStartOffset = startOffset;
307 boolean whiteSpacesOnly = CharArrayUtil.isEmptyOrSpaces(myText, startOffset, iterator.getEndOffset());
309 processBackground(startOffset, iterator.getBackgroundColor());
310 if (!whiteSpacesOnly) {
311 processForeground(startOffset, iterator.getForegroundColor());
312 processFontFamilyName(startOffset, iterator.getFontFamilyName());
313 processFontStyle(startOffset, iterator.getFontStyle());
316 addTextIfPossible(endOffset);
319 private void processFontStyle(int startOffset, int fontStyle) {
320 if (fontStyle != myFontStyle) {
321 addTextIfPossible(startOffset);
322 builder.addFontStyle(fontStyle);
323 myFontStyle = fontStyle;
327 private void processFontFamilyName(int startOffset, String fontName) {
328 String fontFamilyName = FontMapper.getPhysicalFontName(fontName);
329 if (!fontFamilyName.equals(myFontFamilyName)) {
330 addTextIfPossible(startOffset);
331 builder.addFontFamilyName(fontFamilyName);
332 myFontFamilyName = fontFamilyName;
336 private void processForeground(int startOffset, Color foreground) {
337 if (myForeground == null && foreground != null) {
338 addTextIfPossible(startOffset);
339 myForeground = foreground;
340 builder.addForeground(foreground);
342 else if (myForeground != null) {
343 Color c = foreground == null ? myDefaultForeground : foreground;
344 if (!myForeground.equals(c)) {
345 addTextIfPossible(startOffset);
346 builder.addForeground(c);
352 private void processBackground(int startOffset, Color background) {
353 if (myBackground == null && background != null && !myDefaultBackground.equals(background)) {
354 addTextIfPossible(startOffset);
355 myBackground = background;
356 builder.addBackground(background);
358 else if (myBackground != null) {
359 Color c = background == null ? myDefaultBackground : background;
360 if (!myBackground.equals(c)) {
361 addTextIfPossible(startOffset);
362 builder.addBackground(c);
368 private void addTextIfPossible(int endOffset) {
369 if (endOffset <= myStartOffset) {
373 for (int i = myStartOffset; i < endOffset; i++) {
374 char c = myText.charAt(i);
377 if (i + 1 < myText.length() && myText.charAt(i + 1) == '\n') {
378 myIndentSymbolsToStripAtCurrentLine = myIndentSymbolsToStrip;
379 builder.addText(myStartOffset + myOffsetShift, i + myOffsetShift + 1);
380 myStartOffset = i + 2;
382 //noinspection AssignmentToForLoopParameter
386 // Intended fall-through.
388 myIndentSymbolsToStripAtCurrentLine = myIndentSymbolsToStrip;
389 builder.addText(myStartOffset + myOffsetShift, i + myOffsetShift + 1);
390 myStartOffset = i + 1;
392 // Intended fall-through.
395 if (myIndentSymbolsToStripAtCurrentLine > 0) {
396 myIndentSymbolsToStripAtCurrentLine--;
400 default: myIndentSymbolsToStripAtCurrentLine = 0;
404 if (myStartOffset < endOffset) {
405 builder.addText(myStartOffset + myOffsetShift, endOffset + myOffsetShift);
406 myStartOffset = endOffset;
410 private void addCharacter(int position) {
411 builder.addText(position + myOffsetShift, position + myOffsetShift + 1);
415 public SyntaxInfo finish() {
416 return builder.build();
420 private static class MarkupIterator {
421 private final SegmentIterator mySegmentIterator;
422 private final RangeIterator myRangeIterator;
423 private int myCurrentFontStyle;
424 private Color myCurrentForegroundColor;
425 private Color myCurrentBackgroundColor;
427 private MarkupIterator(@NotNull CharSequence charSequence, @NotNull RangeIterator rangeIterator, @NotNull EditorColorsScheme colorsScheme) {
428 myRangeIterator = rangeIterator;
429 mySegmentIterator = new SegmentIterator(charSequence, colorsScheme.getFontPreferences());
432 public boolean atEnd() {
433 return myRangeIterator.atEnd() && mySegmentIterator.atEnd();
436 public void advance() {
437 if (mySegmentIterator.atEnd()) {
438 myRangeIterator.advance();
439 TextAttributes textAttributes = myRangeIterator.getTextAttributes();
440 myCurrentFontStyle = textAttributes == null ? Font.PLAIN : textAttributes.getFontType();
441 myCurrentForegroundColor = textAttributes == null ? null : textAttributes.getForegroundColor();
442 myCurrentBackgroundColor = textAttributes == null ? null : textAttributes.getBackgroundColor();
443 mySegmentIterator.reset(myRangeIterator.getRangeStart(), myRangeIterator.getRangeEnd(), myCurrentFontStyle);
445 mySegmentIterator.advance();
448 public int getStartOffset() {
449 return mySegmentIterator.getCurrentStartOffset();
452 public int getEndOffset() {
453 return mySegmentIterator.getCurrentEndOffset();
456 public int getFontStyle() {
457 return myCurrentFontStyle;
461 public String getFontFamilyName() {
462 return mySegmentIterator.getCurrentFontFamilyName();
466 public Color getForegroundColor() {
467 return myCurrentForegroundColor;
471 public Color getBackgroundColor() {
472 return myCurrentBackgroundColor;
475 public void dispose() {
476 myRangeIterator.dispose();
480 private static class CompositeRangeIterator implements RangeIterator {
481 private final @NotNull Color myDefaultForeground;
482 private final @NotNull Color myDefaultBackground;
483 private final IteratorWrapper[] myIterators;
484 private final TextAttributes myMergedAttributes = new TextAttributes();
485 private int overlappingRangesCount;
486 private int myCurrentStart;
487 private int myCurrentEnd;
489 // iterators have priority corresponding to their order in the parameter list - rightmost having the largest priority
490 public CompositeRangeIterator(@NotNull EditorColorsScheme colorsScheme, RangeIterator... iterators) {
491 myDefaultForeground = colorsScheme.getDefaultForeground();
492 myDefaultBackground = colorsScheme.getDefaultBackground();
493 myIterators = new IteratorWrapper[iterators.length];
494 for (int i = 0; i < iterators.length; i++) {
495 myIterators[i] = new IteratorWrapper(iterators[i], i);
500 public boolean atEnd() {
501 boolean validIteratorExists = false;
502 for (int i = 0; i < myIterators.length; i++) {
503 IteratorWrapper wrapper = myIterators[i];
504 if (wrapper == null) {
507 RangeIterator iterator = wrapper.iterator;
508 if (!iterator.atEnd() || overlappingRangesCount > 0 && (i >= overlappingRangesCount || iterator.getRangeEnd() > myCurrentEnd)) {
509 validIteratorExists = true;
512 return !validIteratorExists;
516 public void advance() {
517 int max = overlappingRangesCount == 0 ? myIterators.length : overlappingRangesCount;
518 for (int i = 0; i < max; i++) {
519 IteratorWrapper wrapper = myIterators[i];
520 if (wrapper == null) {
523 RangeIterator iterator = wrapper.iterator;
524 if (overlappingRangesCount > 0 && iterator.getRangeEnd() > myCurrentEnd) {
527 if (iterator.atEnd()) {
529 myIterators[i] = null;
535 Arrays.sort(myIterators, RANGE_SORTER);
536 myCurrentStart = Math.max(myIterators[0].iterator.getRangeStart(), myCurrentEnd);
537 myCurrentEnd = Integer.MAX_VALUE;
538 //noinspection ForLoopReplaceableByForEach
539 for (int i = 0; i < myIterators.length; i++) {
540 IteratorWrapper wrapper = myIterators[i];
541 if (wrapper == null) {
544 RangeIterator iterator = wrapper.iterator;
546 if (iterator.getRangeStart() > myCurrentStart) {
547 nearestBound = iterator.getRangeStart();
550 nearestBound = iterator.getRangeEnd();
552 myCurrentEnd = Math.min(myCurrentEnd, nearestBound);
554 for (overlappingRangesCount = 1; overlappingRangesCount < myIterators.length; overlappingRangesCount++) {
555 IteratorWrapper wrapper = myIterators[overlappingRangesCount];
556 if (wrapper == null || wrapper.iterator.getRangeStart() > myCurrentStart) {
562 private final Comparator<IteratorWrapper> RANGE_SORTER = new Comparator<IteratorWrapper>() {
564 public int compare(IteratorWrapper o1, IteratorWrapper o2) {
571 int startDiff = Math.max(o1.iterator.getRangeStart(), myCurrentEnd) - Math.max(o2.iterator.getRangeStart(), myCurrentEnd);
572 if (startDiff != 0) {
575 return o2.order - o1.order;
580 public int getRangeStart() {
581 return myCurrentStart;
585 public int getRangeEnd() {
590 public TextAttributes getTextAttributes() {
591 TextAttributes ta = myIterators[0].iterator.getTextAttributes();
592 myMergedAttributes.setAttributes(ta.getForegroundColor(), ta.getBackgroundColor(), null, null, null, ta.getFontType());
593 for (int i = 1; i < overlappingRangesCount; i++) {
594 merge(myIterators[i].iterator.getTextAttributes());
596 return myMergedAttributes;
599 private void merge(TextAttributes attributes) {
600 Color myBackground = myMergedAttributes.getBackgroundColor();
601 if (myBackground == null || myDefaultBackground.equals(myBackground)) {
602 myMergedAttributes.setBackgroundColor(attributes.getBackgroundColor());
604 Color myForeground = myMergedAttributes.getForegroundColor();
605 if (myForeground == null || myDefaultForeground.equals(myForeground)) {
606 myMergedAttributes.setForegroundColor(attributes.getForegroundColor());
608 if (myMergedAttributes.getFontType() == Font.PLAIN) {
609 myMergedAttributes.setFontType(attributes.getFontType());
614 public void dispose() {
615 for (IteratorWrapper wrapper : myIterators) {
616 if (wrapper != null) {
617 wrapper.iterator.dispose();
622 private static class IteratorWrapper {
623 private final RangeIterator iterator;
624 private final int order;
626 private IteratorWrapper(RangeIterator iterator, int order) {
627 this.iterator = iterator;
633 private static class MarkupModelRangeIterator implements RangeIterator {
634 private final boolean myUnsupportedModel;
635 private final int myStartOffset;
636 private final int myEndOffset;
637 private final EditorColorsScheme myColorsScheme;
638 private final Color myDefaultForeground;
639 private final Color myDefaultBackground;
640 private final DisposableIterator<RangeHighlighterEx> myIterator;
642 private int myCurrentStart;
643 private int myCurrentEnd;
644 private TextAttributes myCurrentAttributes;
645 private int myNextStart;
646 private int myNextEnd;
647 private TextAttributes myNextAttributes;
649 private MarkupModelRangeIterator(@Nullable MarkupModel markupModel,
650 @NotNull EditorColorsScheme colorsScheme,
653 myStartOffset = startOffset;
654 myEndOffset = endOffset;
655 myColorsScheme = colorsScheme;
656 myDefaultForeground = colorsScheme.getDefaultForeground();
657 myDefaultBackground = colorsScheme.getDefaultBackground();
658 myUnsupportedModel = !(markupModel instanceof MarkupModelEx);
659 if (myUnsupportedModel) {
663 myIterator = ((MarkupModelEx)markupModel).overlappingIterator(startOffset, endOffset);
665 findNextSuitableRange();
667 catch (RuntimeException e) {
668 myIterator.dispose();
672 myIterator.dispose();
678 public boolean atEnd() {
679 return myUnsupportedModel || myNextAttributes == null;
683 public void advance() {
684 myCurrentStart = myNextStart;
685 myCurrentEnd = myNextEnd;
686 myCurrentAttributes = myNextAttributes;
687 findNextSuitableRange();
690 private void findNextSuitableRange() {
691 myNextAttributes = null;
692 while(myIterator.hasNext()) {
693 RangeHighlighterEx highlighter = myIterator.next();
694 if (highlighter == null || !highlighter.isValid() || !isInterestedInLayer(highlighter.getLayer())) {
697 // LINES_IN_RANGE highlighters are not supported currently
698 myNextStart = Math.max(highlighter.getStartOffset(), myStartOffset);
699 myNextEnd = Math.min(highlighter.getEndOffset(), myEndOffset);
700 if (myNextStart >= myEndOffset) {
703 if (myNextStart < myCurrentEnd) {
704 continue; // overlapping ranges withing document markup model are not supported currently
706 TextAttributes attributes = null;
707 Object tooltip = highlighter.getErrorStripeTooltip();
708 if (tooltip instanceof HighlightInfo) {
709 HighlightInfo info = (HighlightInfo)tooltip;
710 TextAttributesKey key = info.forcedTextAttributesKey;
712 HighlightInfoType type = info.type;
713 key = type.getAttributesKey();
716 attributes = myColorsScheme.getAttributes(key);
719 if (attributes == null) {
722 Color foreground = attributes.getForegroundColor();
723 Color background = attributes.getBackgroundColor();
724 if ((foreground == null || myDefaultForeground.equals(foreground))
725 && (background == null || myDefaultBackground.equals(background))
726 && attributes.getFontType() == Font.PLAIN) {
729 myNextAttributes = attributes;
734 private static boolean isInterestedInLayer(int layer) {
735 return layer != HighlighterLayer.CARET_ROW
736 && layer != HighlighterLayer.SELECTION
737 && layer != HighlighterLayer.ERROR
738 && layer != HighlighterLayer.WARNING;
742 public int getRangeStart() {
743 return myCurrentStart;
747 public int getRangeEnd() {
752 public TextAttributes getTextAttributes() {
753 return myCurrentAttributes;
757 public void dispose() {
758 if (myIterator != null) {
759 myIterator.dispose();
764 private static class HighlighterRangeIterator implements RangeIterator {
765 private static final TextAttributes EMPTY_ATTRIBUTES = new TextAttributes();
767 private final HighlighterIterator myIterator;
768 private final int myStartOffset;
769 private final int myEndOffset;
771 private int myCurrentStart;
772 private int myCurrentEnd;
773 private TextAttributes myCurrentAttributes;
775 public HighlighterRangeIterator(@NotNull EditorHighlighter highlighter, int startOffset, int endOffset) {
776 myStartOffset = startOffset;
777 myEndOffset = endOffset;
778 myIterator = highlighter.createIterator(startOffset);
782 public boolean atEnd() {
783 return myIterator.atEnd() || getCurrentStart() >= myEndOffset;
786 private int getCurrentStart() {
787 return Math.max(myIterator.getStart(), myStartOffset);
790 private int getCurrentEnd() {
791 return Math.min(myIterator.getEnd(), myEndOffset);
795 public void advance() {
796 myCurrentStart = getCurrentStart();
797 myCurrentEnd = getCurrentEnd();
798 myCurrentAttributes = myIterator.getTokenType() == TokenType.BAD_CHARACTER ? EMPTY_ATTRIBUTES : myIterator.getTextAttributes();
799 myIterator.advance();
803 public int getRangeStart() {
804 return myCurrentStart;
808 public int getRangeEnd() {
813 public TextAttributes getTextAttributes() {
814 return myCurrentAttributes;
818 public void dispose() {
822 private interface RangeIterator {
827 TextAttributes getTextAttributes();
831 private static class SegmentIterator {
832 private final CharSequence myCharSequence;
833 private final FontPreferences myFontPreferences;
835 private int myCurrentStartOffset;
836 private int myCurrentOffset;
837 private int myEndOffset;
838 private int myFontStyle;
839 private String myCurrentFontFamilyName;
840 private String myNextFontFamilyName;
842 private SegmentIterator(CharSequence charSequence, FontPreferences fontPreferences) {
843 myCharSequence = charSequence;
844 myFontPreferences = fontPreferences;
847 public void reset(int startOffset, int endOffset, int fontStyle) {
848 myCurrentOffset = startOffset;
849 myEndOffset = endOffset;
850 myFontStyle = fontStyle;
853 public boolean atEnd() {
854 return myCurrentOffset >= myEndOffset;
857 public void advance() {
858 myCurrentFontFamilyName = myNextFontFamilyName;
859 myCurrentStartOffset = myCurrentOffset;
860 for (; myCurrentOffset < myEndOffset; myCurrentOffset++) {
861 FontInfo fontInfo = ComplementaryFontsRegistry.getFontAbleToDisplay(myCharSequence.charAt(myCurrentOffset),
864 String fontFamilyName = fontInfo.getFont().getFamily();
866 if (myCurrentFontFamilyName == null) {
867 myCurrentFontFamilyName = fontFamilyName;
869 else if (!myCurrentFontFamilyName.equals(fontFamilyName)) {
870 myNextFontFamilyName = fontFamilyName;
876 public int getCurrentStartOffset() {
877 return myCurrentStartOffset;
880 public int getCurrentEndOffset() {
881 return myCurrentOffset;
884 public String getCurrentFontFamilyName() {
885 return myCurrentFontFamilyName;
889 public static class RawTextSetter implements CopyPastePreProcessor {
890 private final TextWithMarkupProcessor myProcessor;
892 public RawTextSetter(TextWithMarkupProcessor processor) {
893 myProcessor = processor;
898 public String preprocessOnCopy(PsiFile file, int[] startOffsets, int[] endOffsets, String text) {
899 myProcessor.setRawText(text);
905 public String preprocessOnPaste(Project project, PsiFile file, Editor editor, String text, RawText rawText) {