blob: f45578e09b31dbb650a0448913bdf06737f65d74 [file] [log] [blame]
/*
* Copyright 2000-2014 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor.ex.util;
import com.intellij.diagnostic.Dumpable;
import com.intellij.diagnostic.LogMessageEx;
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.EditorColorsScheme;
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.IterationState;
import com.intellij.openapi.util.Pair;
import com.intellij.openapi.util.SystemInfo;
import com.intellij.openapi.util.TextRange;
import com.intellij.openapi.util.text.StringUtil;
import org.intellij.lang.annotations.JdkConstants;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
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() {
}
public static int getLastVisualLineColumnNumber(@NotNull Editor editor, final int line) {
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;
if (editor instanceof EditorImpl) {
editorInfo = ((EditorImpl)editor).dumpState();
}
else {
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 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();
}
}
/**
* Allows to calculate offset of the given column assuming that it belongs to the given text line identified by the
* given <code>[start; end)</code> intervals.
*
* @param editor editor that is used for representing given text
* @param text target text
* @param start start offset of the logical line that holds target column (inclusive)
* @param end end offset of the logical line that holds target column (exclusive)
* @param columnNumber target column number
* @param tabSize number of desired visual columns to use for tabulation representation
* @param debugBuffer buffer to hold debug info during the processing (if any)
* @return given text offset that identifies the same position that is pointed by the given visual column
*/
public static int calcOffset(@NotNull EditorEx editor,
@NotNull CharSequence text,
int start,
int end,
int columnNumber,
int tabSize,
@Nullable StringBuilder debugBuffer) {
assert start >= 0 : "start (" + start + ") must not be negative. end (" + end + ")";
assert end >= start : "start (" + start + ") must not be greater than end (" + end + ")";
if (debugBuffer != null) {
debugBuffer.append(String.format("Starting calcOffset(). Start=%d, end=%d, column number=%d, tab size=%d%n",
start, end, columnNumber, tabSize));
}
final int maxScanIndex = Math.min(start + columnNumber + 1, end);
SoftWrapModel softWrapModel = editor.getSoftWrapModel();
List<? extends SoftWrap> softWraps = softWrapModel.getSoftWrapsForRange(start, maxScanIndex);
int startToUse = start;
int x = editor.getDocument().getLineNumber(start) == 0 ? editor.getPrefixTextWidthInPixels() : 0;
int[] currentColumn = {0};
for (SoftWrap softWrap : softWraps) {
// There is a possible case that target column points inside soft wrap-introduced virtual space.
if (currentColumn[0] >= columnNumber) {
return startToUse;
}
int result
= calcSoftWrapUnawareOffset(editor, text, startToUse, softWrap.getEnd(), columnNumber, tabSize, x, currentColumn, debugBuffer);
if (result >= 0) {
return result;
}
startToUse = softWrap.getStart();
x = softWrap.getIndentInPixels();
}
// There is a possible case that target column points inside soft wrap-introduced virtual space.
if (currentColumn[0] >= columnNumber) {
return startToUse;
}
int result = calcSoftWrapUnawareOffset(editor, text, startToUse, end, columnNumber, tabSize, x, currentColumn, debugBuffer);
if (result >= 0) {
return result;
}
// We assume that given column points to the virtual space after the line end if control flow reaches this place,
// hence, just return end of line offset then.
if (debugBuffer != null) {
debugBuffer.append(String.format("Returning %d as no match has been found for the target column (%d) at the target range [%d;%d)",
end, columnNumber, start, end));
}
return end;
}
/**
* Tries to match given logical column to the document offset assuming that it's located at <code>[start; end)</code> region.
*
* @param editor editor that is used to represent target document
* @param text target document text
* @param start start offset to check (inclusive)
* @param end end offset to check (exclusive)
* @param columnNumber target logical column number
* @param tabSize user-defined desired number of columns to use for tabulation symbol representation
* @param x <code>'x'</code> coordinate that corresponds to the given <code>'start'</code> offset
* @param currentColumn logical column that corresponds to the given <code>'start'</code> offset
* @param debugBuffer buffer to hold debug info during the processing (if any)
* @return target offset that belongs to the <code>[start; end)</code> range and points to the target logical
* column if any; <code>-1</code> otherwise
*/
private static int calcSoftWrapUnawareOffset(@NotNull Editor editor,
@NotNull CharSequence text,
int start,
int end,
int columnNumber,
int tabSize,
int x,
@NotNull int[] currentColumn,
@Nullable StringBuilder debugBuffer) {
if (debugBuffer != null) {
debugBuffer.append(String.format(
"Starting calcSoftWrapUnawareOffset(). Target range: [%d; %d), target column number to map: %d, tab size: %d, "
+ "x: %d, current column: %d%n", start, end, columnNumber, tabSize, x, currentColumn[0]));
}
// The main problem in a calculation is that target text may contain tabulation symbols and every such symbol may take different
// number of logical columns to represent. E.g. it takes two columns if tab size is four and current column is two; three columns
// if tab size is four and current column is one etc. So, first of all we check if there are tabulation symbols at the target
// text fragment.
boolean useOptimization = true;
boolean hasTabs;
if (editor instanceof EditorImpl && !((EditorImpl)editor).hasTabs()) {
hasTabs = false;
useOptimization = true;
}
else {
hasTabs = false;
int scanEndOffset = Math.min(end, start + columnNumber - currentColumn[0] + 1);
boolean hasNonTabs = false;
for (int i = start; i < scanEndOffset; i++) {
char c = text.charAt(i);
if (debugBuffer != null) {
debugBuffer.append(String.format("Found symbol '%c' at the offset %d%n", c, i));
}
if (c == '\t') {
hasTabs = true;
if (hasNonTabs) {
useOptimization = false;
break;
}
}
else {
hasNonTabs = true;
}
}
}
if (debugBuffer != null) {
debugBuffer.append(String.format("Has tabs: %b, use optimisation: %b%n", hasTabs, useOptimization));
}
// Perform optimized processing if possible. 'Optimized' here means the processing when we exactly know how many logical
// columns are occupied by tabulation symbols.
if (useOptimization) {
if (!hasTabs) {
int result = start + columnNumber - currentColumn[0];
if (result < end) {
return result;
}
else {
currentColumn[0] += end - start;
if (debugBuffer != null) {
debugBuffer.append(String.format("Incrementing 'current column' by %d (new value is %d)%n", end - start, currentColumn[0]));
}
return -1;
}
}
// This variable holds number of 'virtual' tab-introduced columns, e.g. there is a possible case that particular tab owns
// three columns, hence, it increases 'shift' by two (3 - 1).
int shift = 0;
int offset = start;
int prevX = x;
if (debugBuffer != null) {
debugBuffer.append("Processing a string that contains only tabs\n");
}
for (; offset < end && offset + shift + currentColumn[0] < start + columnNumber; offset++) {
final char c = text.charAt(offset);
if (c == '\t') {
int nextX = nextTabStop(prevX, editor, tabSize);
final int columnsShift = columnsNumber(nextX - prevX, getSpaceWidth(Font.PLAIN, editor)) - 1;
if (debugBuffer != null) {
debugBuffer.append(String.format(
"Processing tabulation symbol at the offset %d. Current X: %d, new X: %d, current columns shift: %d, new column shift: %d%n",
offset, prevX, nextX, shift, shift + columnsShift
));
}
shift += columnsShift;
prevX = nextX;
}
}
int diff = start + columnNumber - offset - shift - currentColumn[0];
if (debugBuffer != null) debugBuffer.append(String.format("Resulting diff: %d%n", diff));
if (diff < 0) {
return offset - 1;
}
else if (diff == 0) {
return offset;
}
else {
final int inc = offset - start + shift;
if (debugBuffer != null) {
debugBuffer.append(String.format("Incrementing 'current column' by %d (new value is %d)%n", inc, currentColumn[0] + inc));
}
currentColumn[0] += inc;
return -1;
}
}
// It means that there are tabulation symbols that can't be explicitly mapped to the occupied logical columns number,
// hence, we need to perform special calculations to get know that.
EditorEx editorImpl = (EditorEx)editor;
int offset = start;
IterationState state = new IterationState(editorImpl, start, end, false);
int column;
try {
int fontType = state.getMergedAttributes().getFontType();
column = currentColumn[0];
int spaceSize = getSpaceWidth(fontType, editorImpl);
for (; column < columnNumber && offset < end; offset++) {
if (offset >= state.getEndOffset()) {
state.advance();
fontType = state.getMergedAttributes().getFontType();
}
char c = text.charAt(offset);
if (c == '\t') {
final int newX = nextTabStop(x, editorImpl);
final int columns = columnsNumber(newX - x, spaceSize);
if (debugBuffer != null) {
debugBuffer.append(String.format(
"Processing tabulation at the offset %d. Current X: %d, new X: %d, current column: %d, new column: %d%n",
offset, x, newX, column, column + columns
));
}
x = newX;
column += columns;
}
else {
final int width = charWidth(c, fontType, editorImpl);
if (debugBuffer != null) {
debugBuffer.append(String.format(
"Processing symbol '%c' at the offset %d. Current X: %d, new X: %d%n", c, offset, x, x + width
));
}
x += width;
column++;
}
}
}
finally {
state.dispose();
}
if (column == columnNumber) {
return offset;
}
if (column > columnNumber && offset > 0 && text.charAt(offset - 1) == '\t') {
return offset - 1;
}
currentColumn[0] = column;
return -1;
}
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) {
boolean useOptimization = true;
if (editor != null) {
SoftWrap softWrap = editor.getSoftWrapModel().getSoftWrap(start);
useOptimization = softWrap == null;
}
boolean hasTabs = true;
if (useOptimization) {
if (editor instanceof EditorImpl && !((EditorImpl)editor).hasTabs()) {
hasTabs = false;
}
else {
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) {
int shift = 0;
Document document = editor == null ? null : editor.getDocument();
if (document != null && 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);
}
if (hasTabs) {
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;
}
EditorEx editorImpl = (EditorEx) editor;
return editorImpl.calcColumnNumber(text, start, offset, tabSize);
}
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);
}
}
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());
}
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 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) {
return nextTabStop(x, getSpaceWidth(Font.PLAIN, editor), tabSize);
}
public static int nextTabStop(int x, int spaceWidth, int tabSize) {
if (tabSize <= 0) {
return x + spaceWidth;
}
tabSize *= spaceWidth;
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 columns are necessary for representation of the given char on a screen.
*
* @param c target char
* @param x <code>'x'</code> coordinate of the line where given char is represented that indicates char end location
* @param prevX <code>'x'</code> coordinate of the line where given char is represented that indicates char start location
* @param spaceSize <code>'space'</code> symbol width
* @return number of columns necessary for representation of the given char on a screen.
*/
public static int columnsNumber(char c, int x, int prevX, int spaceSize) {
if (c != '\t') {
return 1;
}
int result = (x - prevX) / spaceSize;
if ((x - prevX) % spaceSize > 0) {
result++;
}
return result;
}
/**
* Allows to answer how many visual columns are occupied by the given width.
*
* @param width target width
* @param spaceSize width of the single space symbol within the target editor
* @return number of visual columns are occupied by the given width
*/
public static int columnsNumber(int width, int spaceSize) {
int result = width / spaceSize;
if (width % spaceSize > 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());
}
/**
* 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
*/
@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 new Pair<LogicalPosition, LogicalPosition>(first, second);
}
public static void scrollToTheEnd(@NotNull Editor editor) {
editor.getCaretModel().moveToOffset(editor.getDocument().getTextLength());
editor.getSelectionModel().removeSelection();
editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE);
}
public static boolean isChangeFontSize(@NotNull MouseWheelEvent e) {
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 = 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);
}
}