/* * Copyright 2000-2017 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.ex.util; import com.intellij.diagnostic.Dumpable; import com.intellij.diagnostic.LogMessageEx; import com.intellij.ide.ui.UISettings; import com.intellij.injected.editor.EditorWindow; import com.intellij.openapi.Disposable; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.application.Result; import com.intellij.openapi.application.WriteAction; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.colors.EditorColorsManager; import com.intellij.openapi.editor.colors.EditorColorsScheme; import com.intellij.openapi.editor.event.EditorFactoryAdapter; import com.intellij.openapi.editor.event.EditorFactoryEvent; import com.intellij.openapi.editor.ex.DocumentBulkUpdateListener; import com.intellij.openapi.editor.ex.DocumentEx; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.impl.ComplementaryFontsRegistry; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.editor.impl.FontInfo; import com.intellij.openapi.editor.impl.ScrollingModelImpl; import com.intellij.openapi.editor.markup.TextAttributes; import com.intellij.openapi.editor.textarea.TextComponentEditor; import com.intellij.openapi.fileEditor.FileEditor; import com.intellij.openapi.fileEditor.TextEditor; import com.intellij.openapi.fileEditor.impl.text.TextEditorImpl; import com.intellij.openapi.fileEditor.impl.text.TextEditorProvider; import com.intellij.openapi.util.*; import com.intellij.openapi.util.registry.Registry; import com.intellij.openapi.util.text.StringUtil; import com.intellij.util.DocumentUtil; import com.intellij.util.ObjectUtils; import com.intellij.util.messages.MessageBusConnection; import org.intellij.lang.annotations.JdkConstants; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import javax.swing.*; import java.awt.*; import java.awt.event.MouseEvent; import java.awt.event.MouseWheelEvent; import java.util.Arrays; import java.util.List; public final class EditorUtil { private static final Logger LOG = Logger.getInstance(EditorUtil.class); private EditorUtil() { } /** * @return true if the editor is in fact an ordinary file editor; * false if the editor is part of EditorTextField, CommitMessage and etc. */ public static boolean isRealFileEditor(@Nullable Editor editor) { return editor != null && TextEditorProvider.getInstance().getTextEditor(editor) instanceof TextEditorImpl; } public static boolean isPasswordEditor(@Nullable Editor editor) { return editor != null && editor.getContentComponent() instanceof JPasswordField; } @Nullable public static EditorEx getEditorEx(@Nullable FileEditor fileEditor) { Editor editor = fileEditor instanceof TextEditor ? ((TextEditor)fileEditor).getEditor() : null; return editor instanceof EditorEx ? (EditorEx)editor : null; } public static int getLastVisualLineColumnNumber(@NotNull Editor editor, final int line) { if (editor instanceof EditorImpl) { LogicalPosition lineEndPosition = editor.visualToLogicalPosition(new VisualPosition(line, Integer.MAX_VALUE)); int lineEndOffset = editor.logicalPositionToOffset(lineEndPosition); return editor.offsetToVisualPosition(lineEndOffset, true, true).column; } Document document = editor.getDocument(); int lastLine = document.getLineCount() - 1; if (lastLine < 0) { return 0; } // Filter all lines that are not shown because of collapsed folding region. VisualPosition visStart = new VisualPosition(line, 0); LogicalPosition logStart = editor.visualToLogicalPosition(visStart); int lastLogLine = logStart.line; while (lastLogLine < document.getLineCount() - 1) { logStart = new LogicalPosition(logStart.line + 1, logStart.column); VisualPosition tryVisible = editor.logicalToVisualPosition(logStart); if (tryVisible.line != visStart.line) break; lastLogLine = logStart.line; } int resultLogLine = Math.min(lastLogLine, lastLine); VisualPosition resVisStart = editor.offsetToVisualPosition(document.getLineStartOffset(resultLogLine)); VisualPosition resVisEnd = editor.offsetToVisualPosition(document.getLineEndOffset(resultLogLine)); // Target logical line is not soft wrap affected. if (resVisStart.line == resVisEnd.line) { return resVisEnd.column; } int visualLinesToSkip = line - resVisStart.line; List<? extends SoftWrap> softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(resultLogLine); for (int i = 0; i < softWraps.size(); i++) { SoftWrap softWrap = softWraps.get(i); CharSequence text = document.getCharsSequence(); if (visualLinesToSkip <= 0) { VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); int result = visual.column; int x = editor.visualPositionToXY(visual).x; // We need to add width of the next symbol because current result column points to the last symbol before the soft wrap. return result + textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); } int softWrapLineFeeds = StringUtil.countNewLines(softWrap.getText()); if (softWrapLineFeeds < visualLinesToSkip) { visualLinesToSkip -= softWrapLineFeeds; continue; } // Target visual column is located on the last visual line of the current soft wrap. if (softWrapLineFeeds == visualLinesToSkip) { if (i >= softWraps.size() - 1) { return resVisEnd.column; } // We need to find visual column for line feed of the next soft wrap. SoftWrap nextSoftWrap = softWraps.get(i + 1); VisualPosition visual = editor.offsetToVisualPosition(nextSoftWrap.getStart() - 1); int result = visual.column; int x = editor.visualPositionToXY(visual).x; // We need to add symbol width because current column points to the last symbol before the next soft wrap; result += textWidthInColumns(editor, text, nextSoftWrap.getStart() - 1, nextSoftWrap.getStart(), x); int lineFeedIndex = StringUtil.indexOf(nextSoftWrap.getText(), '\n'); result += textWidthInColumns(editor, nextSoftWrap.getText(), 0, lineFeedIndex, 0); return result; } // Target visual column is the one before line feed introduced by the current soft wrap. int softWrapStartOffset = 0; int softWrapEndOffset = 0; int softWrapTextLength = softWrap.getText().length(); while (visualLinesToSkip-- > 0) { softWrapStartOffset = softWrapEndOffset + 1; if (softWrapStartOffset >= softWrapTextLength) { assert false; return resVisEnd.column; } softWrapEndOffset = StringUtil.indexOf(softWrap.getText(), '\n', softWrapStartOffset, softWrapTextLength); if (softWrapEndOffset < 0) { assert false; return resVisEnd.column; } } VisualPosition visual = editor.offsetToVisualPosition(softWrap.getStart() - 1); int result = visual.column; // Column of the symbol just before the soft wrap int x = editor.visualPositionToXY(visual).x; // Target visual column is located on the last visual line of the current soft wrap. result += textWidthInColumns(editor, text, softWrap.getStart() - 1, softWrap.getStart(), x); result += calcColumnNumber(editor, softWrap.getText(), softWrapStartOffset, softWrapEndOffset); return result; } CharSequence editorInfo = "editor's class: " + editor.getClass() + ", all soft wraps: " + editor.getSoftWrapModel().getSoftWrapsForRange(0, document.getTextLength()) + ", fold regions: " + Arrays.toString(editor.getFoldingModel().getAllFoldRegions()); LogMessageEx.error(LOG, "Can't calculate last visual column", String.format( "Target visual line: %d, mapped logical line: %d, visual lines range for the mapped logical line: [%s]-[%s], soft wraps for " + "the target logical line: %s. Editor info: %s", line, resultLogLine, resVisStart, resVisEnd, softWraps, editorInfo )); return resVisEnd.column; } public static int getVisualLineEndOffset(@NotNull Editor editor, int line) { VisualPosition endLineVisualPosition = new VisualPosition(line, getLastVisualLineColumnNumber(editor, line)); LogicalPosition endLineLogicalPosition = editor.visualToLogicalPosition(endLineVisualPosition); return editor.logicalPositionToOffset(endLineLogicalPosition); } public static float calcVerticalScrollProportion(@NotNull Editor editor) { Rectangle viewArea = editor.getScrollingModel().getVisibleAreaOnScrollingFinished(); if (viewArea.height == 0) { return 0; } LogicalPosition pos = editor.getCaretModel().getLogicalPosition(); Point location = editor.logicalPositionToXY(pos); return (location.y - viewArea.y) / (float) viewArea.height; } public static void setVerticalScrollProportion(@NotNull Editor editor, float proportion) { Rectangle viewArea = editor.getScrollingModel().getVisibleArea(); LogicalPosition caretPosition = editor.getCaretModel().getLogicalPosition(); Point caretLocation = editor.logicalPositionToXY(caretPosition); int yPos = caretLocation.y; yPos -= viewArea.height * proportion; editor.getScrollingModel().scrollVertically(yPos); } public static int calcRelativeCaretPosition(@NotNull Editor editor) { int caretY = editor.getCaretModel().getVisualPosition().line * editor.getLineHeight(); int viewAreaPosition = editor.getScrollingModel().getVisibleAreaOnScrollingFinished().y; return caretY - viewAreaPosition; } public static void setRelativeCaretPosition(@NotNull Editor editor, int position) { int caretY = editor.getCaretModel().getVisualPosition().line * editor.getLineHeight(); editor.getScrollingModel().scrollVertically(caretY - position); } public static void fillVirtualSpaceUntilCaret(@NotNull Editor editor) { final LogicalPosition position = editor.getCaretModel().getLogicalPosition(); fillVirtualSpaceUntil(editor, position.column, position.line); } public static void fillVirtualSpaceUntil(@NotNull final Editor editor, int columnNumber, int lineNumber) { final int offset = editor.logicalPositionToOffset(new LogicalPosition(lineNumber, columnNumber)); final String filler = EditorModificationUtil.calcStringToFillVirtualSpace(editor); if (!filler.isEmpty()) { new WriteAction(){ @Override protected void run(@NotNull Result result) throws Throwable { editor.getDocument().insertString(offset, filler); editor.getCaretModel().moveToOffset(offset + filler.length()); } }.execute(); } } private static int getTabLength(int colNumber, int tabSize) { if (tabSize <= 0) { tabSize = 1; } return tabSize - colNumber % tabSize; } public static int calcColumnNumber(@NotNull Editor editor, @NotNull CharSequence text, int start, int offset) { return calcColumnNumber(editor, text, start, offset, getTabSize(editor)); } public static int calcColumnNumber(@Nullable Editor editor, @NotNull CharSequence text, final int start, final int offset, final int tabSize) { if (editor instanceof TextComponentEditor) { return offset - start; } boolean useOptimization = true; if (editor != null) { SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start); useOptimization = softWrap == null; } if (useOptimization) { boolean hasNonTabs = false; for (int i = start; i < offset; i++) { if (text.charAt(i) == '\t') { if (hasNonTabs) { useOptimization = false; break; } } else { hasNonTabs = true; } } } if (editor != null && useOptimization) { Document document = editor.getDocument(); if (start < offset - 1 && document.getLineNumber(start) != document.getLineNumber(offset - 1)) { String editorInfo = editor instanceof EditorImpl ? ". Editor info: " + ((EditorImpl)editor).dumpState() : ""; String documentInfo; if (text instanceof Dumpable) { documentInfo = ((Dumpable)text).dumpState(); } else { documentInfo = "Text holder class: " + text.getClass(); } LogMessageEx.error( LOG, "detected incorrect offset -> column number calculation", "start: " + start + ", given offset: " + offset+", given tab size: " + tabSize + ". "+documentInfo+ editorInfo); } } int shift = 0; for (int i = start; i < offset; i++) { char c = text.charAt(i); if (c == '\t') { shift += getTabLength(i + shift - start, tabSize) - 1; } } return offset - start + shift; } public static void setHandCursor(@NotNull Editor view) { Cursor c = Cursor.getPredefinedCursor(Cursor.HAND_CURSOR); // XXX: Workaround, simply view.getContentComponent().setCursor(c) doesn't work if (view.getContentComponent().getCursor() != c) { view.getContentComponent().setCursor(c); } } @NotNull public static FontInfo fontForChar(final char c, @JdkConstants.FontStyle int style, @NotNull Editor editor) { EditorColorsScheme colorsScheme = editor.getColorsScheme(); return ComplementaryFontsRegistry.getFontAbleToDisplay(c, style, colorsScheme.getFontPreferences(), FontInfo.getFontRenderContext(editor.getContentComponent())); } public static Icon scaleIconAccordingEditorFont(Icon icon, Editor editor) { if (Registry.is("editor.scale.gutter.icons") && editor instanceof EditorImpl && icon instanceof ScalableIcon) { float scale = ((EditorImpl)editor).getScale(); if (Math.abs(1f - scale) > 0.1f) { return ((ScalableIcon)icon).scale(scale); } } return icon; } public static int charWidth(char c, @JdkConstants.FontStyle int fontType, @NotNull Editor editor) { return fontForChar(c, fontType, editor).charWidth(c); } public static int getSpaceWidth(@JdkConstants.FontStyle int fontType, @NotNull Editor editor) { int width = charWidth(' ', fontType, editor); return width > 0 ? width : 1; } public static int getPlainSpaceWidth(@NotNull Editor editor) { return getSpaceWidth(Font.PLAIN, editor); } public static int getTabSize(@NotNull Editor editor) { return editor.getSettings().getTabSize(editor.getProject()); } public static int nextTabStop(int x, @NotNull Editor editor) { int tabSize = getTabSize(editor); if (tabSize <= 0) { tabSize = 1; } return nextTabStop(x, editor, tabSize); } public static int nextTabStop(int x, @NotNull Editor editor, int tabSize) { int leftInset = editor.getContentComponent().getInsets().left; return nextTabStop(x - leftInset, getSpaceWidth(Font.PLAIN, editor), tabSize) + leftInset; } public static int nextTabStop(int x, int plainSpaceWidth, int tabSize) { if (tabSize <= 0) { return x + plainSpaceWidth; } tabSize *= plainSpaceWidth; int nTabs = x / tabSize; return (nTabs + 1) * tabSize; } public static int textWidthInColumns(@NotNull Editor editor, @NotNull CharSequence text, int start, int end, int x) { int startToUse = start; int lastTabSymbolIndex = -1; // Skip all lines except the last. loop: for (int i = end - 1; i >= start; i--) { switch (text.charAt(i)) { case '\n': startToUse = i + 1; break loop; case '\t': if (lastTabSymbolIndex < 0) lastTabSymbolIndex = i; } } // Tabulation is assumed to be the only symbol which representation may take various number of visual columns, hence, // we return eagerly if no such symbol is found. if (lastTabSymbolIndex < 0) { return end - startToUse; } int result = 0; int spaceSize = getSpaceWidth(Font.PLAIN, editor); // Calculate number of columns up to the latest tabulation symbol. for (int i = startToUse; i <= lastTabSymbolIndex; i++) { SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(i); if (softWrap != null) { x = softWrap.getIndentInPixels(); } char c = text.charAt(i); int prevX = x; switch (c) { case '\t': x = nextTabStop(x, editor); result += columnsNumber(x - prevX, spaceSize); break; case '\n': x = result = 0; break; default: x += charWidth(c, Font.PLAIN, editor); result++; } } // Add remaining tabulation-free columns. result += end - lastTabSymbolIndex - 1; return result; } /** * Allows to answer how many visual columns are occupied by the given width. * * @param width target width * @param plainSpaceSize width of the single space symbol within the target editor (in plain font style) * @return number of visual columns are occupied by the given width */ public static int columnsNumber(int width, int plainSpaceSize) { int result = width / plainSpaceSize; if (width % plainSpaceSize > 0) { result++; } return result; } /** * Allows to answer what width in pixels is required to draw fragment of the given char array from <code>[start; end)</code> interval * at the given editor. * <p/> * Tabulation symbols is processed specially, i.e. it's ta * <p/> * <b>Note:</b> it's assumed that target text fragment remains to the single line, i.e. line feed symbols within it are not * treated specially. * * @param editor editor that will be used for target text representation * @param text target text holder * @param start offset within the given char array that points to target text start (inclusive) * @param end offset within the given char array that points to target text end (exclusive) * @param fontType font type to use for target text representation * @param x <code>'x'</code> coordinate that should be used as a starting point for target text representation. * It's necessity is implied by the fact that IDEA editor may represent tabulation symbols in any range * from <code>[1; tab size]</code> (check {@link #nextTabStop(int, Editor)} for more details) * @return width in pixels required for target text representation */ public static int textWidth(@NotNull Editor editor, @NotNull CharSequence text, int start, int end, @JdkConstants.FontStyle int fontType, int x) { int result = 0; for (int i = start; i < end; i++) { char c = text.charAt(i); if (c != '\t') { FontInfo font = fontForChar(c, fontType, editor); result += font.charWidth(c); continue; } result += nextTabStop(x + result, editor) - result - x; } return result; } /** * Delegates to the {@link #calcSurroundingRange(Editor, VisualPosition, VisualPosition)} with the * {@link CaretModel#getVisualPosition() caret visual position} as an argument. * * @param editor target editor * @return surrounding logical positions * @see #calcSurroundingRange(Editor, VisualPosition, VisualPosition) */ public static Pair<LogicalPosition, LogicalPosition> calcCaretLineRange(@NotNull Editor editor) { return calcSurroundingRange(editor, editor.getCaretModel().getVisualPosition(), editor.getCaretModel().getVisualPosition()); } public static Pair<LogicalPosition, LogicalPosition> calcCaretLineRange(@NotNull Caret caret) { return calcSurroundingRange(caret.getEditor(), caret.getVisualPosition(), caret.getVisualPosition()); } /** * Calculates logical positions that surround given visual positions and conform to the following criteria: * <pre> * <ul> * <li>located at the start or the end of the visual line;</li> * <li>doesn't have soft wrap at the target offset;</li> * </ul> * </pre> * Example: * <pre> * first line [soft-wrap] some [start-position] text [end-position] [fold-start] fold line 1 * fold line 2 * fold line 3[fold-end] [soft-wrap] end text * </pre> * The very first and the last positions will be returned here. * * @param editor target editor to use * @param start target start coordinate * @param end target end coordinate * @return pair of the closest surrounding non-soft-wrapped logical positions for the visual line start and end * * @see #getNotFoldedLineStartOffset(Editor, int) * @see #getNotFoldedLineEndOffset(Editor, int) */ @SuppressWarnings("AssignmentToForLoopParameter") public static Pair<LogicalPosition, LogicalPosition> calcSurroundingRange(@NotNull Editor editor, @NotNull VisualPosition start, @NotNull VisualPosition end) { final Document document = editor.getDocument(); final FoldingModel foldingModel = editor.getFoldingModel(); LogicalPosition first = editor.visualToLogicalPosition(new VisualPosition(start.line, 0)); for ( int line = first.line, offset = document.getLineStartOffset(line); offset >= 0; offset = document.getLineStartOffset(line)) { final FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(offset); if (foldRegion == null) { first = new LogicalPosition(line, 0); break; } final int foldEndLine = document.getLineNumber(foldRegion.getStartOffset()); if (foldEndLine <= line) { first = new LogicalPosition(line, 0); break; } line = foldEndLine; } LogicalPosition second = editor.visualToLogicalPosition(new VisualPosition(end.line, 0)); for ( int line = second.line, offset = document.getLineEndOffset(line); offset <= document.getTextLength(); offset = document.getLineEndOffset(line)) { final FoldRegion foldRegion = foldingModel.getCollapsedRegionAtOffset(offset); if (foldRegion == null) { second = new LogicalPosition(line + 1, 0); break; } final int foldEndLine = document.getLineNumber(foldRegion.getEndOffset()); if (foldEndLine <= line) { second = new LogicalPosition(line + 1, 0); break; } line = foldEndLine; } if (second.line >= document.getLineCount()) { second = editor.offsetToLogicalPosition(document.getTextLength()); } return Pair.create(first, second); } /** * Finds the start offset of visual line at which given offset is located, not taking soft wraps into account. */ public static int getNotFoldedLineStartOffset(@NotNull Editor editor, int offset) { while(true) { offset = DocumentUtil.getLineStartOffset(offset, editor.getDocument()); FoldRegion foldRegion = editor.getFoldingModel().getCollapsedRegionAtOffset(offset - 1); if (foldRegion == null || foldRegion.getStartOffset() >= offset) { break; } offset = foldRegion.getStartOffset(); } return offset; } /** * Finds the end offset of visual line at which given offset is located, not taking soft wraps into account. */ public static int getNotFoldedLineEndOffset(@NotNull Editor editor, int offset) { while(true) { offset = getLineEndOffset(offset, editor.getDocument()); FoldRegion foldRegion = editor.getFoldingModel().getCollapsedRegionAtOffset(offset); if (foldRegion == null || foldRegion.getEndOffset() <= offset) { break; } offset = foldRegion.getEndOffset(); } return offset; } private static int getLineEndOffset(int offset, Document document) { if (offset >= document.getTextLength()) { return offset; } int lineNumber = document.getLineNumber(offset); return document.getLineEndOffset(lineNumber); } public static void scrollToTheEnd(@NotNull Editor editor) { editor.getSelectionModel().removeSelection(); int lastLine = Math.max(0, editor.getDocument().getLineCount() - 1); if (editor.getCaretModel().getLogicalPosition().line == lastLine) { editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength()); } else { editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(lastLine, 0)); } editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } public static boolean isChangeFontSize(@NotNull MouseWheelEvent e) { if (e.getWheelRotation() == 0) return false; return SystemInfo.isMac ? !e.isControlDown() && e.isMetaDown() && !e.isAltDown() && !e.isShiftDown() : e.isControlDown() && !e.isMetaDown() && !e.isAltDown() && !e.isShiftDown(); } public static boolean inVirtualSpace(@NotNull Editor editor, @NotNull LogicalPosition logicalPosition) { return !editor.offsetToLogicalPosition(editor.logicalPositionToOffset(logicalPosition)).equals(logicalPosition); } public static void reinitSettings() { EditorFactory.getInstance().refreshAllEditors(); } @NotNull public static TextRange getSelectionInAnyMode(Editor editor) { SelectionModel selection = editor.getSelectionModel(); int[] starts = selection.getBlockSelectionStarts(); int[] ends = selection.getBlockSelectionEnds(); int start = starts.length > 0 ? starts[0] : selection.getSelectionStart(); int end = ends.length > 0 ? ends[ends.length - 1] : selection.getSelectionEnd(); return TextRange.create(start, end); } public static int yPositionToLogicalLine(@NotNull Editor editor, @NotNull MouseEvent event) { return yPositionToLogicalLine(editor, event.getY()); } public static int yPositionToLogicalLine(@NotNull Editor editor, @NotNull Point point) { return yPositionToLogicalLine(editor, point.y); } public static int yPositionToLogicalLine(@NotNull Editor editor, int y) { int line = editor instanceof EditorImpl ? ((EditorImpl)editor).yToVisibleLine(y): y / editor.getLineHeight(); return line > 0 ? editor.visualToLogicalPosition(new VisualPosition(line, 0)).line : 0; } public static boolean isAtLineEnd(@NotNull Editor editor, int offset) { Document document = editor.getDocument(); if (offset < 0 || offset > document.getTextLength()) { return false; } int line = document.getLineNumber(offset); return offset == document.getLineEndOffset(line); } /** * Setting selection using {@link SelectionModel#setSelection(int, int)} or {@link Caret#setSelection(int, int)} methods can result * in resulting selection range to be larger than requested (in case requested range intersects with collapsed fold regions). * This method will make sure interfering collapsed regions are expanded first, so that resulting selection range is exactly as * requested. */ public static void setSelectionExpandingFoldedRegionsIfNeeded(@NotNull Editor editor, int startOffset, int endOffset) { FoldingModel foldingModel = editor.getFoldingModel(); FoldRegion startFoldRegion = foldingModel.getCollapsedRegionAtOffset(startOffset); if (startFoldRegion != null && (startFoldRegion.getStartOffset() == startOffset || startFoldRegion.isExpanded())) { startFoldRegion = null; } FoldRegion endFoldRegion = foldingModel.getCollapsedRegionAtOffset(endOffset); if (endFoldRegion != null && (endFoldRegion.getStartOffset() == endOffset || endFoldRegion.isExpanded())) { endFoldRegion = null; } if (startFoldRegion != null || endFoldRegion != null) { final FoldRegion finalStartFoldRegion = startFoldRegion; final FoldRegion finalEndFoldRegion = endFoldRegion; foldingModel.runBatchFoldingOperation(() -> { if (finalStartFoldRegion != null) finalStartFoldRegion.setExpanded(true); if (finalEndFoldRegion != null) finalEndFoldRegion.setExpanded(true); }); } editor.getSelectionModel().setSelection(startOffset, endOffset); } public static Font getEditorFont() { EditorColorsScheme scheme = EditorColorsManager.getInstance().getGlobalScheme(); int size = UISettings.getInstance().getPresentationMode() ? UISettings.getInstance().getPresentationModeFontSize() - 4 : scheme.getEditorFontSize(); return new Font(scheme.getEditorFontName(), Font.PLAIN, size); } /** * Number of virtual soft wrap introduced lines on a current logical line before the visual position that corresponds * to the current logical position. * * @see LogicalPosition#softWrapLinesOnCurrentLogicalLine */ public static int getSoftWrapCountAfterLineStart(@NotNull Editor editor, @NotNull LogicalPosition position) { if (position.visualPositionAware) { return position.softWrapLinesOnCurrentLogicalLine; } int startOffset = editor.getDocument().getLineStartOffset(position.line); int endOffset = editor.logicalPositionToOffset(position); return editor.getSoftWrapModel().getSoftWrapsForRange(startOffset, endOffset).size(); } public static boolean attributesImpactFontStyleOrColor(@Nullable TextAttributes attributes) { return attributes == TextAttributes.ERASE_MARKER || (attributes != null && (attributes.getFontType() != Font.PLAIN || attributes.getForegroundColor() != null)); } public static boolean isCurrentCaretPrimary(@NotNull Editor editor) { return editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret(); } public static void disposeWithEditor(@NotNull Editor editor, @NotNull Disposable disposable) { ApplicationManager.getApplication().assertIsDispatchThread(); if (Disposer.isDisposed(disposable)) return; if (editor.isDisposed()) { Disposer.dispose(disposable); return; } // for injected editors disposal will happen only when host editor is disposed, // but this seems to be the best we can do (there are no notifications on disposal of injected editor) Editor hostEditor = editor instanceof EditorWindow ? ((EditorWindow)editor).getDelegate() : editor; EditorFactory.getInstance().addEditorFactoryListener(new EditorFactoryAdapter() { @Override public void editorReleased(@NotNull EditorFactoryEvent event) { if (event.getEditor() == hostEditor) { Disposer.dispose(disposable); } } }, disposable); } public static void runBatchFoldingOperationOutsideOfBulkUpdate(@NotNull Editor editor, @NotNull Runnable operation) { DocumentEx document = ObjectUtils.tryCast(editor.getDocument(), DocumentEx.class); if (document != null && document.isInBulkUpdate()) { MessageBusConnection connection = ApplicationManager.getApplication().getMessageBus().connect(); disposeWithEditor(editor, connection); connection.subscribe(DocumentBulkUpdateListener.TOPIC, new DocumentBulkUpdateListener.Adapter() { @Override public void updateFinished(@NotNull Document doc) { if (doc == editor.getDocument()) { editor.getFoldingModel().runBatchFoldingOperation(operation); connection.disconnect(); } } }); } else { editor.getFoldingModel().runBatchFoldingOperation(operation); } } public static void runWithAnimationDisabled(@NotNull Editor editor, @NotNull Runnable taskWithScrolling) { ScrollingModel scrollingModel = editor.getScrollingModel(); if (!(scrollingModel instanceof ScrollingModelImpl)) { taskWithScrolling.run(); } else { boolean animationWasEnabled = ((ScrollingModelImpl)scrollingModel).isAnimationEnabled(); scrollingModel.disableAnimation(); try { taskWithScrolling.run(); } finally { if (animationWasEnabled) scrollingModel.enableAnimation(); } } } }