| /* |
| * 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). |
| * <p/> |
| * 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. |
| * <p/> |
| * 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<Integer> 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; |
| * <code>'-1'</code> 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<? extends SoftWrap> 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; |
| * <code>'-1'</code> 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<VisualPosition> 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 <code>false</code> if the operation cannot be performed due to current caret being at the edge (top or bottom) of the document, |
| * and <code>true</code> 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; |
| } |
| } |