/* * 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. */ /* * Created by IntelliJ IDEA. * User: max * Date: Jun 6, 2002 * Time: 4:54:58 PM * To change template for new class use * Code Style | Class Templates options (Tools | IDE Options). */ package com.intellij.openapi.editor.actions; import com.intellij.codeStyle.CodeStyleFacade; import com.intellij.ide.ui.customization.CustomActionsSchema; import com.intellij.openapi.actionSystem.ActionGroup; import com.intellij.openapi.actionSystem.ActionManager; import com.intellij.openapi.actionSystem.ActionPlaces; import com.intellij.openapi.actionSystem.ActionPopupMenu; import com.intellij.openapi.editor.*; import com.intellij.openapi.editor.event.EditorMouseEvent; import com.intellij.openapi.editor.event.EditorMouseEventArea; import com.intellij.openapi.editor.event.EditorMouseListener; import com.intellij.openapi.editor.ex.EditorEx; import com.intellij.openapi.editor.ex.util.EditorUtil; import com.intellij.openapi.editor.impl.EditorImpl; import com.intellij.openapi.fileTypes.FileType; import com.intellij.openapi.project.Project; import com.intellij.openapi.util.Key; import com.intellij.openapi.util.text.StringUtil; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.EditorPopupHandler; import org.jetbrains.annotations.NotNull; import java.awt.*; import java.awt.event.MouseEvent; import java.util.List; public class EditorActionUtil { /** * Editor actions may be invoked multiple ways - programmatically, via keyboard/mouse shortcut, main/context menu etc. * Action processing may also interfere with standard editor behavior (caret position change, selection change etc). *

* E.g. consider a situation when context menu is shown on right mouse click - * {@link EditorMouseListener#mousePressed(EditorMouseEvent) the contract says} that no common actions have been performed yet. * However, some actions may operate on an 'active element' (an element under caret), hence, they would incorrectly because the * caret position has not been changed yet. *

* We address that problem by providing a special key that is intended to hold 'expected caret offset', i.e. offset where we * expect the caret to be located at the near future. */ public static final Key EXPECTED_CARET_OFFSET = Key.create("expectedEditorOffset"); protected static final Object EDIT_COMMAND_GROUP = Key.create("EditGroup"); public static final Object DELETE_COMMAND_GROUP = Key.create("DeleteGroup"); private EditorActionUtil() { } /** * Tries to change given editor's viewport position in vertical dimension by the given number of visual lines. * * @param editor target editor which viewport position should be changed * @param lineShift defines viewport position's vertical change length * @param columnShift defines viewport position's horizontal change length * @param moveCaret flag that identifies whether caret should be moved if its current position becomes off-screen */ public static void scrollRelatively(@NotNull Editor editor, int lineShift, int columnShift, boolean moveCaret) { if (lineShift != 0) { editor.getScrollingModel().scrollVertically( editor.getScrollingModel().getVerticalScrollOffset() + lineShift * editor.getLineHeight() ); } if (columnShift != 0) { editor.getScrollingModel().scrollHorizontally( editor.getScrollingModel().getHorizontalScrollOffset() + columnShift * EditorUtil.getSpaceWidth(Font.PLAIN, editor) ); } if (!moveCaret) { return; } Rectangle viewRectangle = editor.getScrollingModel().getVisibleArea(); int lineNumber = editor.getCaretModel().getVisualPosition().line; VisualPosition startPos = editor.xyToVisualPosition(new Point(0, viewRectangle.y)); int start = startPos.line + 1; VisualPosition endPos = editor.xyToVisualPosition(new Point(0, viewRectangle.y + viewRectangle.height)); int end = endPos.line - 2; if (lineNumber < start) { editor.getCaretModel().moveCaretRelatively(0, start - lineNumber, false, false, true); } else if (lineNumber > end) { editor.getCaretModel().moveCaretRelatively(0, end - lineNumber, false, false, true); } } public static void moveCaretRelativelyAndScroll(@NotNull Editor editor, int columnShift, int lineShift, boolean withSelection) { Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); VisualPosition pos = editor.getCaretModel().getVisualPosition(); Point caretLocation = editor.visualPositionToXY(pos); int caretVShift = caretLocation.y - visibleArea.y; editor.getCaretModel().moveCaretRelatively(columnShift, lineShift, withSelection, false, false); //editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); VisualPosition caretPos = editor.getCaretModel().getVisualPosition(); Point caretLocation2 = editor.visualPositionToXY(caretPos); final boolean scrollToCaret = !(editor instanceof EditorImpl) || ((EditorImpl)editor).isScrollToCaret(); if (scrollToCaret) { editor.getScrollingModel().scrollVertically(caretLocation2.y - caretVShift); } } public static void indentLine(Project project, @NotNull Editor editor, int lineNumber, int indent) { EditorSettings editorSettings = editor.getSettings(); Document document = editor.getDocument(); int spacesEnd = 0; int lineStart = 0; int tabsEnd = 0; if (lineNumber < document.getLineCount()) { lineStart = document.getLineStartOffset(lineNumber); int lineEnd = document.getLineEndOffset(lineNumber); spacesEnd = lineStart; CharSequence text = document.getCharsSequence(); boolean inTabs = true; for (; spacesEnd <= lineEnd; spacesEnd++) { if (spacesEnd == lineEnd) { break; } char c = text.charAt(spacesEnd); if (c != '\t') { if (inTabs) { inTabs = false; tabsEnd = spacesEnd; } if (c != ' ') break; } } if (inTabs) { tabsEnd = lineEnd; } } int oldLength = editor.offsetToLogicalPosition(spacesEnd).column; tabsEnd = editor.offsetToLogicalPosition(tabsEnd).column; int newLength = oldLength + indent; if (newLength < 0) { newLength = 0; } tabsEnd += indent; if (tabsEnd < 0) tabsEnd = 0; if (!shouldUseSmartTabs(project, editor)) tabsEnd = newLength; StringBuilder buf = new StringBuilder(newLength); int tabSize = editorSettings.getTabSize(project); for (int i = 0; i < newLength;) { if (tabSize > 0 && editorSettings.isUseTabCharacter(project) && i + tabSize <= tabsEnd) { buf.append('\t'); i += tabSize; } else { buf.append(' '); i++; } } int newCaretOffset = editor.getCaretModel().getOffset(); if (newCaretOffset >= spacesEnd) { newCaretOffset += buf.length() - (spacesEnd - lineStart); } if (buf.length() > 0) { if (spacesEnd > lineStart) { document.replaceString(lineStart, spacesEnd, buf.toString()); } else { document.insertString(lineStart, buf.toString()); } } else { if (spacesEnd > lineStart) { document.deleteString(lineStart, spacesEnd); } } editor.getCaretModel().moveToOffset(Math.min(document.getTextLength(), newCaretOffset)); } private static boolean shouldUseSmartTabs(Project project, @NotNull Editor editor) { if (!(editor instanceof EditorEx)) return false; VirtualFile file = ((EditorEx)editor).getVirtualFile(); FileType fileType = file == null ? null : file.getFileType(); return fileType != null && CodeStyleFacade.getInstance(project).isSmartTabs(fileType); } public static boolean isWordStart(@NotNull CharSequence text, int offset, boolean isCamel) { char prev = offset > 0 ? text.charAt(offset - 1) : 0; char current = text.charAt(offset); final boolean firstIsIdentifierPart = Character.isJavaIdentifierPart(prev); final boolean secondIsIdentifierPart = Character.isJavaIdentifierPart(current); if (!firstIsIdentifierPart && secondIsIdentifierPart) { return true; } if (isCamel && firstIsIdentifierPart && secondIsIdentifierPart && isHumpBound(text, offset, true)) { return true; } return (Character.isWhitespace(prev) || firstIsIdentifierPart) && !Character.isWhitespace(current) && !secondIsIdentifierPart; } private static boolean isLowerCaseOrDigit(char c) { return Character.isLowerCase(c) || Character.isDigit(c); } public static boolean isWordEnd(@NotNull CharSequence text, int offset, boolean isCamel) { char prev = offset > 0 ? text.charAt(offset - 1) : 0; char current = text.charAt(offset); char next = offset + 1 < text.length() ? text.charAt(offset + 1) : 0; final boolean firstIsIdentifierPart = Character.isJavaIdentifierPart(prev); final boolean secondIsIdentifierPart = Character.isJavaIdentifierPart(current); if (firstIsIdentifierPart && !secondIsIdentifierPart) { return true; } if (isCamel) { if (firstIsIdentifierPart && (Character.isLowerCase(prev) && Character.isUpperCase(current) || prev != '_' && current == '_' || Character.isUpperCase(prev) && Character.isUpperCase(current) && Character.isLowerCase(next))) { return true; } } return !Character.isWhitespace(prev) && !firstIsIdentifierPart && (Character.isWhitespace(current) || secondIsIdentifierPart); } /** * Depending on the current caret position and 'smart Home' editor settings, moves caret to the start of current visual line * or to the first non-whitespace character on it. * * @param isWithSelection if true - sets selection from old caret position to the new one, if false - clears selection * * @see com.intellij.openapi.editor.actions.EditorActionUtil#moveCaretToLineStartIgnoringSoftWraps(com.intellij.openapi.editor.Editor) */ public static void moveCaretToLineStart(@NotNull Editor editor, boolean isWithSelection) { Document document = editor.getDocument(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); EditorSettings editorSettings = editor.getSettings(); int logCaretLine = caretModel.getLogicalPosition().line; VisualPosition currentVisCaret = caretModel.getVisualPosition(); VisualPosition caretLogLineStartVis = editor.offsetToVisualPosition(document.getLineStartOffset(logCaretLine)); if (currentVisCaret.line > caretLogLineStartVis.line) { // Caret is located not at the first visual line of soft-wrapped logical line. if (editorSettings.isSmartHome()) { moveCaretToStartOfSoftWrappedLine(editor, currentVisCaret, currentVisCaret.line - caretLogLineStartVis.line); } else { caretModel.moveToVisualPosition(new VisualPosition(currentVisCaret.line, 0)); } setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return; } // Skip folded lines. int logLineToUse = logCaretLine - 1; while (logLineToUse >= 0 && editor.offsetToVisualPosition(document.getLineEndOffset(logLineToUse)).line == currentVisCaret.line) { logLineToUse--; } logLineToUse++; if (logLineToUse >= document.getLineCount() || !editorSettings.isSmartHome()) { editor.getCaretModel().moveToLogicalPosition(new LogicalPosition(logLineToUse, 0)); } else if (logLineToUse == logCaretLine) { int line = currentVisCaret.line; int column; if (currentVisCaret.column == 0) { column = findSmartIndentColumn(editor, currentVisCaret.line); } else { column = findFirstNonSpaceColumnOnTheLine(editor, currentVisCaret.line); if (column >= currentVisCaret.column) { column = 0; } } caretModel.moveToVisualPosition(new VisualPosition(line, Math.max(column, 0))); } else { LogicalPosition logLineEndLog = editor.offsetToLogicalPosition(document.getLineEndOffset(logLineToUse)); VisualPosition logLineEndVis = editor.logicalToVisualPosition(logLineEndLog); if (logLineEndLog.softWrapLinesOnCurrentLogicalLine > 0) { moveCaretToStartOfSoftWrappedLine(editor, logLineEndVis, logLineEndLog.softWrapLinesOnCurrentLogicalLine); } else { int line = logLineEndVis.line; if (currentVisCaret.column == 0 && editorSettings.isSmartHome()) { findSmartIndentColumn(editor, line); } int column = 0; caretModel.moveToVisualPosition(new VisualPosition(line, column)); } } setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } private static void moveCaretToStartOfSoftWrappedLine(@NotNull Editor editor, VisualPosition currentVisual, int softWrappedLines) { CaretModel caretModel = editor.getCaretModel(); LogicalPosition startLineLogical = editor.visualToLogicalPosition(new VisualPosition(currentVisual.line, 0)); int startLineOffset = editor.logicalPositionToOffset(startLineLogical); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); SoftWrap softWrap = softWrapModel.getSoftWrap(startLineOffset); if (softWrap == null) { // Don't expect to be here. int column = findFirstNonSpaceColumnOnTheLine(editor, currentVisual.line); int columnToMove = column; if (column < 0 || currentVisual.column <= column && currentVisual.column > 0) { columnToMove = 0; } caretModel.moveToVisualPosition(new VisualPosition(currentVisual.line, columnToMove)); return; } if (currentVisual.column > softWrap.getIndentInColumns()) { caretModel.moveToOffset(softWrap.getStart()); } else if (currentVisual.column > 0) { caretModel.moveToVisualPosition(new VisualPosition(currentVisual.line, 0)); } else { // We assume that caret is already located at zero visual column of soft-wrapped line if control flow reaches this place. int newVisualCaretLine = currentVisual.line - 1; int newVisualCaretColumn = -1; if (softWrappedLines > 1) { int offset = editor.logicalPositionToOffset(editor.visualToLogicalPosition(new VisualPosition(newVisualCaretLine, 0))); SoftWrap prevLineSoftWrap = softWrapModel.getSoftWrap(offset); if (prevLineSoftWrap != null) { newVisualCaretColumn = prevLineSoftWrap.getIndentInColumns(); } } if (newVisualCaretColumn < 0) { newVisualCaretColumn = findFirstNonSpaceColumnOnTheLine(editor, newVisualCaretLine); } caretModel.moveToVisualPosition(new VisualPosition(newVisualCaretLine, newVisualCaretColumn)); } } private static int findSmartIndentColumn(@NotNull Editor editor, int visualLine) { for (int i = visualLine; i >= 0; i--) { int column = findFirstNonSpaceColumnOnTheLine(editor, i); if (column >= 0) { return column; } } return 0; } /** * Tries to find visual column that points to the first non-white space symbol at the visual line at the given editor. * * @param editor target editor * @param visualLineNumber target visual line * @return visual column that points to the first non-white space symbol at the target visual line if the one exists; * '-1' otherwise */ public static int findFirstNonSpaceColumnOnTheLine(@NotNull Editor editor, int visualLineNumber) { Document document = editor.getDocument(); VisualPosition visLine = new VisualPosition(visualLineNumber, 0); int logLine = editor.visualToLogicalPosition(visLine).line; int logLineStartOffset = document.getLineStartOffset(logLine); int logLineEndOffset = document.getLineEndOffset(logLine); LogicalPosition logLineStart = editor.offsetToLogicalPosition(logLineStartOffset); VisualPosition visLineStart = editor.logicalToVisualPosition(logLineStart); boolean softWrapIntroducedLine = visLineStart.line != visualLineNumber; if (!softWrapIntroducedLine) { int offset = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), logLineStartOffset, logLineEndOffset); if (offset >= 0) { return EditorUtil.calcColumnNumber(editor, document.getCharsSequence(), logLineStartOffset, offset); } else { return -1; } } int lineFeedsToSkip = visualLineNumber - visLineStart.line; List softWraps = editor.getSoftWrapModel().getSoftWrapsForLine(logLine); for (SoftWrap softWrap : softWraps) { CharSequence softWrapText = softWrap.getText(); int softWrapLineFeedsNumber = StringUtil.countNewLines(softWrapText); if (softWrapLineFeedsNumber < lineFeedsToSkip) { lineFeedsToSkip -= softWrapLineFeedsNumber; continue; } // Point to the first non-white space symbol at the target soft wrap visual line or to the first non-white space symbol // of document line that follows it if possible. int softWrapTextLength = softWrapText.length(); boolean skip = true; for (int j = 0; j < softWrapTextLength; j++) { if (softWrapText.charAt(j) == '\n') { skip = --lineFeedsToSkip > 0; continue; } if (skip) { continue; } int nextSoftWrapLineFeedOffset = StringUtil.indexOf(softWrapText, '\n', j, softWrapTextLength); int end = findFirstNonSpaceOffsetInRange(softWrapText, j, softWrapTextLength); if (end >= 0) { // Non space symbol is contained at soft wrap text after offset that corresponds to the target visual line start. if (nextSoftWrapLineFeedOffset < 0 || end < nextSoftWrapLineFeedOffset) { return EditorUtil.calcColumnNumber(editor, softWrapText, j, end); } else { return -1; } } if (nextSoftWrapLineFeedOffset >= 0) { // There are soft wrap-introduced visual lines after the target one return -1; } } int end = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), softWrap.getStart(), logLineEndOffset); if (end >= 0) { return EditorUtil.calcColumnNumber(editor, document.getCharsSequence(), softWrap.getStart(), end); } else { return -1; } } return -1; } public static int findFirstNonSpaceOffsetOnTheLine(@NotNull Document document, int lineNumber) { int lineStart = document.getLineStartOffset(lineNumber); int lineEnd = document.getLineEndOffset(lineNumber); int result = findFirstNonSpaceOffsetInRange(document.getCharsSequence(), lineStart, lineEnd); return result >= 0 ? result : lineEnd; } /** * Tries to find non white space symbol at the given range at the given document. * * @param text text to be inspected * @param start target start offset (inclusive) * @param end target end offset (exclusive) * @return index of the first non-white space character at the given document at the given range if the one is found; * '-1' otherwise */ public static int findFirstNonSpaceOffsetInRange(@NotNull CharSequence text, int start, int end) { for (; start < end; start++) { char c = text.charAt(start); if (c != ' ' && c != '\t') { return start; } } return -1; } public static void moveCaretToLineEnd(@NotNull Editor editor, boolean isWithSelection) { Document document = editor.getDocument(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); SoftWrapModel softWrapModel = editor.getSoftWrapModel(); int lineNumber = editor.getCaretModel().getLogicalPosition().line; if (lineNumber >= document.getLineCount()) { LogicalPosition pos = new LogicalPosition(lineNumber, 0); editor.getCaretModel().moveToLogicalPosition(pos); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); return; } VisualPosition currentVisualCaret = editor.getCaretModel().getVisualPosition(); VisualPosition visualEndOfLineWithCaret = new VisualPosition(currentVisualCaret.line, EditorUtil.getLastVisualLineColumnNumber(editor, currentVisualCaret.line)); // There is a possible case that the caret is already located at the visual end of line and the line is soft wrapped. // We want to move the caret to the end of the next visual line then. if (currentVisualCaret.equals(visualEndOfLineWithCaret)) { LogicalPosition logical = editor.visualToLogicalPosition(visualEndOfLineWithCaret); int offset = editor.logicalPositionToOffset(logical); if (offset < editor.getDocument().getTextLength()) { SoftWrap softWrap = softWrapModel.getSoftWrap(offset); if (softWrap == null) { // Same offset may correspond to positions on different visual lines in case of soft wraps presence // (all soft-wrap introduced virtual text is mapped to the same offset as the first document symbol after soft wrap). // Hence, we check for soft wraps presence at two offsets. softWrap = softWrapModel.getSoftWrap(offset + 1); } int line = currentVisualCaret.line; int column = currentVisualCaret.column; if (softWrap != null) { line++; column = EditorUtil.getLastVisualLineColumnNumber(editor, line); } visualEndOfLineWithCaret = new VisualPosition(line, column); } } LogicalPosition logLineEnd = editor.visualToLogicalPosition(visualEndOfLineWithCaret); int offset = editor.logicalPositionToOffset(logLineEnd); lineNumber = logLineEnd.line; int newOffset = offset; CharSequence text = document.getCharsSequence(); for (int i = newOffset - 1; i >= document.getLineStartOffset(lineNumber); i--) { if (softWrapModel.getSoftWrap(i) != null) { newOffset = offset; break; } if (text.charAt(i) != ' ' && text.charAt(i) != '\t') { break; } newOffset = i; } // Move to the calculated end of visual line if caret is located on a last non-white space symbols on a line and there are // remaining white space symbols. if (newOffset == offset || newOffset == caretModel.getOffset()) { caretModel.moveToVisualPosition(visualEndOfLineWithCaret); } else { caretModel.moveToOffset(newOffset); } editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); } public static void moveCaretToNextWord(@NotNull Editor editor, boolean isWithSelection, boolean camel) { Document document = editor.getDocument(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); int offset = caretModel.getOffset(); CharSequence text = document.getCharsSequence(); if (offset == document.getTextLength()) { return; } int newOffset = offset + 1; int lineNumber = caretModel.getLogicalPosition().line; if (lineNumber >= document.getLineCount()) return; int maxOffset = document.getLineEndOffset(lineNumber); if (newOffset > maxOffset) { if (lineNumber + 1 >= document.getLineCount()) { return; } maxOffset = document.getLineEndOffset(lineNumber + 1); } for (; newOffset < maxOffset; newOffset++) { if (isWordStart(text, newOffset, camel)) { break; } } caretModel.moveToOffset(newOffset); if (editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret()) { editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); } private static void setupSelection(@NotNull Editor editor, boolean isWithSelection, int selectionStart, @NotNull LogicalPosition blockSelectionStart) { SelectionModel selectionModel = editor.getSelectionModel(); CaretModel caretModel = editor.getCaretModel(); if (isWithSelection) { if (editor.isColumnMode() && !caretModel.supportsMultipleCarets()) { selectionModel.setBlockSelection(blockSelectionStart, caretModel.getLogicalPosition()); } else { selectionModel.setSelection(selectionStart, caretModel.getVisualPosition(), caretModel.getOffset()); } } else { selectionModel.removeSelection(); } selectNonexpandableFold(editor); } private static final Key PREV_POS = Key.create("PREV_POS"); public static void selectNonexpandableFold(@NotNull Editor editor) { final CaretModel caretModel = editor.getCaretModel(); final VisualPosition pos = caretModel.getVisualPosition(); VisualPosition prevPos = editor.getUserData(PREV_POS); if (prevPos != null) { int columnShift = pos.line == prevPos.line ? pos.column - prevPos.column : 0; int caret = caretModel.getOffset(); final FoldRegion collapsedUnderCaret = editor.getFoldingModel().getCollapsedRegionAtOffset(caret); if (collapsedUnderCaret != null && collapsedUnderCaret.shouldNeverExpand()) { if (caret > collapsedUnderCaret.getStartOffset() && columnShift > 0) { caretModel.moveToOffset(collapsedUnderCaret.getEndOffset()); } else if (caret + 1 < collapsedUnderCaret.getEndOffset() && columnShift < 0) { caretModel.moveToOffset(collapsedUnderCaret.getStartOffset()); } editor.getSelectionModel().setSelection(collapsedUnderCaret.getStartOffset(), collapsedUnderCaret.getEndOffset()); } } editor.putUserData(PREV_POS, pos); } public static void moveCaretToPreviousWord(@NotNull Editor editor, boolean isWithSelection, boolean camel) { Document document = editor.getDocument(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); int offset = editor.getCaretModel().getOffset(); if (offset == 0) return; int lineNumber = editor.getCaretModel().getLogicalPosition().line; CharSequence text = document.getCharsSequence(); int newOffset = offset - 1; int minOffset = lineNumber > 0 ? document.getLineEndOffset(lineNumber - 1) : 0; for (; newOffset > minOffset; newOffset--) { if (isWordStart(text, newOffset, camel)) break; } editor.getCaretModel().moveToOffset(newOffset); if (editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret()) { editor.getScrollingModel().scrollToCaret(ScrollType.RELATIVE); } setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); } public static void moveCaretPageUp(@NotNull Editor editor, boolean isWithSelection) { int lineHeight = editor.getLineHeight(); Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); int linesIncrement = visibleArea.height / lineHeight; editor.getScrollingModel().scrollVertically(visibleArea.y - visibleArea.y % lineHeight - linesIncrement * lineHeight); int lineShift = -linesIncrement; editor.getCaretModel().moveCaretRelatively(0, lineShift, isWithSelection, editor.isColumnMode(), true); } public static void moveCaretPageDown(@NotNull Editor editor, boolean isWithSelection) { int lineHeight = editor.getLineHeight(); Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); int linesIncrement = visibleArea.height / lineHeight; int allowedBottom = ((EditorEx)editor).getContentSize().height - visibleArea.height; editor.getScrollingModel().scrollVertically( Math.min(allowedBottom, visibleArea.y - visibleArea.y % lineHeight + linesIncrement * lineHeight)); editor.getCaretModel().moveCaretRelatively(0, linesIncrement, isWithSelection, editor.isColumnMode(), true); } public static void moveCaretPageTop(@NotNull Editor editor, boolean isWithSelection) { int lineHeight = editor.getLineHeight(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); int lineNumber = visibleArea.y / lineHeight; if (visibleArea.y % lineHeight > 0) { lineNumber++; } VisualPosition pos = new VisualPosition(lineNumber, editor.getCaretModel().getVisualPosition().column); editor.getCaretModel().moveToVisualPosition(pos); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); } public static void moveCaretPageBottom(@NotNull Editor editor, boolean isWithSelection) { int lineHeight = editor.getLineHeight(); SelectionModel selectionModel = editor.getSelectionModel(); int selectionStart = selectionModel.getLeadSelectionOffset(); CaretModel caretModel = editor.getCaretModel(); LogicalPosition blockSelectionStart = selectionModel.hasBlockSelection() ? selectionModel.getBlockStart() : caretModel.getLogicalPosition(); Rectangle visibleArea = editor.getScrollingModel().getVisibleArea(); int lineNumber = (visibleArea.y + visibleArea.height) / lineHeight - 1; VisualPosition pos = new VisualPosition(lineNumber, editor.getCaretModel().getVisualPosition().column); editor.getCaretModel().moveToVisualPosition(pos); setupSelection(editor, isWithSelection, selectionStart, blockSelectionStart); } public static EditorPopupHandler createEditorPopupHandler(@NotNull final String groupId) { return new EditorPopupHandler() { @Override public void invokePopup(final EditorMouseEvent event) { if (!event.isConsumed() && event.getArea() == EditorMouseEventArea.EDITING_AREA) { ActionGroup group = (ActionGroup)CustomActionsSchema.getInstance().getCorrectedAction(groupId); ActionPopupMenu popupMenu = ActionManager.getInstance().createActionPopupMenu(ActionPlaces.EDITOR_POPUP, group); MouseEvent e = event.getMouseEvent(); final Component c = e.getComponent(); if (c != null && c.isShowing()) { popupMenu.getComponent().show(c, e.getX(), e.getY()); } e.consume(); } } }; } public static boolean isHumpBound(@NotNull CharSequence editorText, int offset, boolean start) { final char prevChar = editorText.charAt(offset - 1); final char curChar = editorText.charAt(offset); final char nextChar = offset + 1 < editorText.length() ? editorText.charAt(offset + 1) : 0; // 0x00 is not lowercase. return isLowerCaseOrDigit(prevChar) && Character.isUpperCase(curChar) || start && prevChar == '_' && curChar != '_' || !start && prevChar != '_' && curChar == '_' || start && prevChar == '$' && Character.isLetterOrDigit(curChar) || !start && Character.isLetterOrDigit(prevChar) && curChar == '$' || Character.isUpperCase(prevChar) && Character.isUpperCase(curChar) && Character.isLowerCase(nextChar); } /** * This method moves caret to the nearest preceding visual line start, which is not a soft line wrap * * @see com.intellij.openapi.editor.ex.util.EditorUtil#calcCaretLineRange(com.intellij.openapi.editor.Editor) * @see com.intellij.openapi.editor.actions.EditorActionUtil#moveCaretToLineStart(com.intellij.openapi.editor.Editor, boolean) */ public static void moveCaretToLineStartIgnoringSoftWraps(@NotNull Editor editor) { editor.getCaretModel().moveToLogicalPosition(EditorUtil.calcCaretLineRange(editor).first); } /** * This method will make required expansions of collapsed region to make given offset 'visible'. */ public static void makePositionVisible(@NotNull final Editor editor, final int offset) { FoldingModel foldingModel = editor.getFoldingModel(); FoldRegion collapsedRegionAtOffset; while ((collapsedRegionAtOffset = foldingModel.getCollapsedRegionAtOffset(offset)) != null) { final FoldRegion region = collapsedRegionAtOffset; foldingModel.runBatchFoldingOperation(new Runnable() { @Override public void run() { region.setExpanded(true); } }); } } /** * Clones caret in a given direction if it's possible. If there already exists a caret at the given direction, removes the current caret. * * @param editor editor to perform operation in * @param caret caret to work on * @param above whether to clone the caret above or below * @return false if the operation cannot be performed due to current caret being at the edge (top or bottom) of the document, * and true otherwise */ public static boolean cloneOrRemoveCaret(Editor editor, Caret caret, boolean above) { if (above && caret.getLogicalPosition().line == 0) { return false; } if (!above && caret.getLogicalPosition().line == editor.getDocument().getLineCount() - 1) { return false; } if (caret.clone(above) == null) { editor.getCaretModel().removeCaret(caret); } return true; } }