mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-08-17 16:31:45 +02:00
Compare commits
25 Commits
Author | SHA1 | Date | |
---|---|---|---|
![]() |
132f52785c | ||
![]() |
c556ec2001 | ||
![]() |
d49683ab2f | ||
![]() |
810c3cd561 | ||
![]() |
b909157f4b | ||
![]() |
21c1232ba6 | ||
![]() |
ff61a42670 | ||
![]() |
f160d855c0 | ||
![]() |
51685a2094 | ||
![]() |
487c71ec15 | ||
![]() |
39aa60850d | ||
![]() |
872921e6b7 | ||
![]() |
89788df95c | ||
![]() |
6ccd8ed0b8 | ||
![]() |
aa7e3bfa69 | ||
![]() |
00154f2b9f | ||
![]() |
531a9c28ae | ||
![]() |
56c4e3e31f | ||
![]() |
ef2497cadc | ||
![]() |
95f56a8869 | ||
![]() |
f5b1112304 | ||
![]() |
333a5be30b | ||
![]() |
6c9e697892 | ||
![]() |
7663eb531e | ||
![]() |
5529bf284a |
@@ -52,6 +52,8 @@ Contributors:
|
||||
* [Vasily Alferov](mailto:ya-ikmik2012@yandex.ru)
|
||||
* [Vitalii Karavaev](mailto:fkve97@gmail.com)
|
||||
* [John Lin](mailto:johnlinp@gmail.com)
|
||||
* [Alex Plate](mailto:alexpl292@gmail.com)
|
||||
* [Matt Ellis](mailto:m.t.ellis@gmail.com)
|
||||
|
||||
If you are a contributor and your name is not listed here, feel free to
|
||||
contact the maintainer.
|
||||
|
10
CHANGES.md
10
CHANGES.md
@@ -16,13 +16,21 @@ It is important to distinguish EAP from traditional pre-release software.
|
||||
Please note that the quality of EAP versions may at times be way below even
|
||||
usual beta standards.
|
||||
|
||||
|
||||
To Be Released
|
||||
--------------
|
||||
|
||||
...
|
||||
|
||||
|
||||
0.51, 2019-02-12
|
||||
----------------
|
||||
|
||||
* [VIM-1558](https://youtrack.jetbrains.com/issue/VIM-1558) Fixed scrolling for code with block inlays in Rider 2018.3
|
||||
* [VIM-1187](https://youtrack.jetbrains.com/issue/VIM-1187) Improved performance of `set relativelinenumber` on large files
|
||||
* [VIM-620](https://youtrack.jetbrains.com/issue/VIM-620) Fixed handling `<C-O>` and `<Esc>` in Insert and Replace modes
|
||||
* [VIM-798](https://youtrack.jetbrains.com/issue/VIM-798) Allow arrow keys for window navigation commands
|
||||
|
||||
|
||||
0.50, 2018-10-18
|
||||
----------------
|
||||
|
||||
|
@@ -23,7 +23,7 @@ IdeaVim
|
||||
|
||||
IdeaVim is a Vim emulation plugin for IDEs based on the IntelliJ platform.
|
||||
IdeaVim can be used with IntelliJ IDEA, PyCharm, CLion, PhpStorm, WebStorm,
|
||||
RubyMine, AppCode, DataGrip, GoLand, Cursive, and Android Studio.
|
||||
RubyMine, AppCode, DataGrip, GoLand, Rider, Cursive, and Android Studio.
|
||||
|
||||
Resources:
|
||||
|
||||
|
@@ -36,6 +36,7 @@ intellij {
|
||||
updateSinceUntilBuild false
|
||||
downloadSources Boolean.valueOf(downloadIdeaSources)
|
||||
instrumentCode Boolean.valueOf(instrumentPluginCode)
|
||||
intellijRepo = "https://www.jetbrains.com/intellij-repository"
|
||||
|
||||
publishPlugin {
|
||||
channels publishChannels.split(',')
|
||||
@@ -50,5 +51,4 @@ repositories {
|
||||
|
||||
dependencies {
|
||||
compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlinVersion"
|
||||
compile "org.jetbrains.kotlin:kotlin-runtime:$kotlinVersion"
|
||||
}
|
||||
|
@@ -1,9 +1,9 @@
|
||||
ideaVersion IC-2018.2.5
|
||||
ideaVersion IC-2018.3
|
||||
downloadIdeaSources true
|
||||
instrumentPluginCode true
|
||||
version SNAPSHOT
|
||||
javaVersion 1.8
|
||||
kotlinVersion 1.2.71
|
||||
kotlinVersion 1.3.11
|
||||
publishUsername username
|
||||
publishPassword password
|
||||
publishChannels eap
|
||||
|
@@ -2,6 +2,10 @@
|
||||
<name>IdeaVim</name>
|
||||
<id>IdeaVIM</id>
|
||||
<change-notes><![CDATA[
|
||||
<p>0.51:</p>
|
||||
<ul>
|
||||
<li>Various bug fixes</li>
|
||||
</ul>
|
||||
<p>0.50:</p>
|
||||
<ul>
|
||||
<li>Moved "Vim Emulation" settings into "File | Settings | Editor"</li>
|
||||
@@ -46,7 +50,7 @@
|
||||
<version>SNAPSHOT</version>
|
||||
<vendor>JetBrains</vendor>
|
||||
|
||||
<idea-version since-build="181.0"/>
|
||||
<idea-version since-build="183.2940.10"/>
|
||||
|
||||
<!-- Mark the plugin as compatible with RubyMine and other products based on the IntelliJ platform -->
|
||||
<depends>com.intellij.modules.lang</depends>
|
||||
@@ -352,6 +356,9 @@
|
||||
<action id="VimRedo" class="com.maddyhome.idea.vim.action.change.RedoAction" text="Redo"/>
|
||||
<action id="VimUndo" class="com.maddyhome.idea.vim.action.change.UndoAction" text="Undo"/>
|
||||
|
||||
<!-- Internal -->
|
||||
<action id="VimInternalAddInlays" class="com.maddyhome.idea.vim.action.internal.AddInlaysAction" text="Vim (internal) add test inlays" internal="true"/>
|
||||
|
||||
<!-- Keys -->
|
||||
<action id="VimShortcutKeyAction" class="com.maddyhome.idea.vim.action.VimShortcutKeyAction" text="Shortcuts"/>
|
||||
<action id="VimOperatorAction" class="com.maddyhome.idea.vim.action.change.OperatorAction" text="Operator"/>
|
||||
|
@@ -35,7 +35,7 @@ public class InsertInsertAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandler {
|
||||
public void execute(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
VimPlugin.getChange().processInsert(InjectedLanguageUtil.getTopLevelEditor(editor), context);
|
||||
VimPlugin.getChange().processInsert(InjectedLanguageUtil.getTopLevelEditor(editor));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
148
src/com/maddyhome/idea/vim/action/internal/AddInlaysAction.java
Normal file
148
src/com/maddyhome/idea/vim/action/internal/AddInlaysAction.java
Normal file
@@ -0,0 +1,148 @@
|
||||
package com.maddyhome.idea.vim.action.internal;
|
||||
|
||||
import com.intellij.ide.ui.AntialiasingType;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.CommonDataKeys;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.editor.impl.FontInfo;
|
||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.font.FontRenderContext;
|
||||
import java.awt.font.LineMetrics;
|
||||
import java.util.Random;
|
||||
|
||||
public class AddInlaysAction extends AnAction {
|
||||
private static Random random = new Random();
|
||||
|
||||
@Override
|
||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||
DataContext dataContext = e.getDataContext();
|
||||
Editor editor = getEditor(dataContext);
|
||||
if (editor == null) return;
|
||||
|
||||
InlayModel inlayModel = editor.getInlayModel();
|
||||
|
||||
Document document = editor.getDocument();
|
||||
int lineCount = document.getLineCount();
|
||||
for (int i = random.nextInt(10); i < lineCount; ) {
|
||||
|
||||
int offset = document.getLineStartOffset(i);
|
||||
|
||||
// Mostly above
|
||||
boolean above = random.nextInt(10) > 3;
|
||||
|
||||
// Mostly do one, but occasionally throw in a bunch
|
||||
int count = random.nextInt(10) > 7 ? random.nextInt(5) : 1;
|
||||
for (int j = 0; j < count; j++) {
|
||||
|
||||
float factor = Math.max(1.75f * random.nextFloat(), 0.9f);
|
||||
String text = String.format("---------- %s line %d ----------", above ? "above" : "below", i + 1);
|
||||
|
||||
inlayModel.addBlockElement(offset, true, above, 0, new MyBlockRenderer(factor, text));
|
||||
}
|
||||
|
||||
// Every 10 lines +/- 3 lines
|
||||
i += 10 + (random.nextInt(6) - 3);
|
||||
}
|
||||
}
|
||||
|
||||
protected Editor getEditor(@NotNull DataContext dataContext) {
|
||||
return CommonDataKeys.EDITOR.getData(dataContext);
|
||||
}
|
||||
|
||||
private static class MyBlockRenderer implements EditorCustomElementRenderer {
|
||||
|
||||
private static Key<MyFontMetrics> HINT_FONT_METRICS = Key.create("DummyInlayFontMetrics");
|
||||
private float factor;
|
||||
private String text;
|
||||
|
||||
MyBlockRenderer(float factor, String text) {
|
||||
this.factor = factor;
|
||||
this.text = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calcWidthInPixels(@NotNull Inlay inlay) {
|
||||
Editor editor = inlay.getEditor();
|
||||
FontMetrics fontMetrics = getFontMetrics(editor).metrics;
|
||||
return doCalcWidth(text, fontMetrics);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calcHeightInPixels(@NotNull Inlay inlay) {
|
||||
Editor editor = inlay.getEditor();
|
||||
FontMetrics fontMetrics = getFontMetrics(editor).metrics;
|
||||
return fontMetrics.getHeight();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paint(@NotNull Inlay inlay,
|
||||
@NotNull Graphics g,
|
||||
@NotNull Rectangle targetRegion,
|
||||
@NotNull TextAttributes textAttributes) {
|
||||
Editor editor = inlay.getEditor();
|
||||
FontMetrics fontMetrics = getFontMetrics(editor).metrics;
|
||||
LineMetrics lineMetrics = fontMetrics.getLineMetrics(text, g);
|
||||
|
||||
g.setColor(JBColor.GRAY);
|
||||
g.setFont(fontMetrics.getFont());
|
||||
g.drawString(text, 0, targetRegion.y + (int)(lineMetrics.getHeight() - lineMetrics.getDescent()));
|
||||
g.setColor(JBColor.LIGHT_GRAY);
|
||||
g.drawRect(targetRegion.x, targetRegion.y, targetRegion.width, targetRegion.height);
|
||||
}
|
||||
|
||||
private MyFontMetrics getFontMetrics(Editor editor) {
|
||||
String familyName = UIManager.getFont("Label.font").getFamily();
|
||||
int size = (int)(Math.max(1, editor.getColorsScheme().getEditorFontSize() - 1) * factor);
|
||||
MyFontMetrics metrics = editor.getUserData(HINT_FONT_METRICS);
|
||||
if (metrics != null && !metrics.isActual(editor, familyName, size)) {
|
||||
metrics = null;
|
||||
}
|
||||
if (metrics == null) {
|
||||
metrics = new MyFontMetrics(editor, familyName, size);
|
||||
editor.putUserData(HINT_FONT_METRICS, metrics);
|
||||
}
|
||||
return metrics;
|
||||
}
|
||||
|
||||
private int doCalcWidth(String text, FontMetrics fontMetrics) {
|
||||
return (text == null) ? 0 : fontMetrics.stringWidth(text);
|
||||
}
|
||||
|
||||
|
||||
protected class MyFontMetrics {
|
||||
private FontMetrics metrics;
|
||||
|
||||
MyFontMetrics(Editor editor, String familyName, int size) {
|
||||
Font font = UIUtil.getFontWithFallback(familyName, Font.PLAIN, size);
|
||||
FontRenderContext context = getCurrentContext(editor);
|
||||
metrics = FontInfo.getFontMetrics(font, context);
|
||||
// We assume this will be a better approximation to a real line height for a given font
|
||||
}
|
||||
|
||||
public boolean isActual(Editor editor, String familyName, int size) {
|
||||
Font font = metrics.getFont();
|
||||
if (!familyName.equals(font.getFamily()) || size != font.getSize()) return false;
|
||||
FontRenderContext currentContext = getCurrentContext(editor);
|
||||
return currentContext.equals(metrics.getFontRenderContext());
|
||||
}
|
||||
|
||||
private FontRenderContext getCurrentContext(Editor editor) {
|
||||
FontRenderContext editorContext = FontInfo.getFontRenderContext(editor.getContentComponent());
|
||||
return new FontRenderContext(editorContext.getTransform(),
|
||||
AntialiasingType.getKeyForCurrentScope(false), editor instanceof EditorImpl
|
||||
? ((EditorImpl)editor).myFractionalMetricsHintValue
|
||||
: RenderingHints.VALUE_FRACTIONALMETRICS_OFF);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@@ -24,9 +24,7 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.action.motion.MotionEditorAction;
|
||||
import com.maddyhome.idea.vim.command.Argument;
|
||||
import com.maddyhome.idea.vim.handler.ExecuteMethodNotOverriddenException;
|
||||
import com.maddyhome.idea.vim.handler.MotionEditorActionHandler;
|
||||
import gherkin.lexer.Vi;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollFirstScreenLineAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.getRawCount(), cmd.getCount(), false);
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.getRawCount(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -36,16 +36,14 @@ public class MotionScrollFirstScreenLinePageStartAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
int raw = cmd.getRawCount();
|
||||
int cnt = cmd.getCount();
|
||||
if (raw == 0) {
|
||||
int lines = EditorHelper.getScreenHeight(editor);
|
||||
|
||||
return VimPlugin.getMotion().scrollLine(editor, lines);
|
||||
}
|
||||
else {
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, raw, cnt, true);
|
||||
int line = cmd.getRawCount();
|
||||
if (line == 0) {
|
||||
final int nextVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor) + 1;
|
||||
line = EditorHelper.visualLineToLogicalLine(editor, nextVisualLine) + 1; // rawCount is 1 based
|
||||
}
|
||||
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, line, true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollFirstScreenLineStartAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.getRawCount(), cmd.getCount(), true);
|
||||
return VimPlugin.getMotion().scrollLineToFirstScreenLine(editor, cmd.getRawCount(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollHalfPageDownAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollHalfPage(editor, 1, cmd.getRawCount());
|
||||
return VimPlugin.getMotion().scrollScreen(editor, cmd.getRawCount(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollHalfPageUpAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollHalfPage(editor, -1, cmd.getRawCount());
|
||||
return VimPlugin.getMotion().scrollScreen(editor, cmd.getRawCount(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollLastScreenLineAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.getRawCount(), cmd.getCount(), false);
|
||||
return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.getRawCount(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -23,6 +23,7 @@ import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.actionSystem.EditorAction;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.Command;
|
||||
import com.maddyhome.idea.vim.group.MotionGroup;
|
||||
import com.maddyhome.idea.vim.handler.EditorActionHandlerBase;
|
||||
import com.maddyhome.idea.vim.helper.EditorHelper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -36,16 +37,26 @@ public class MotionScrollLastScreenLinePageStartAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
int raw = cmd.getRawCount();
|
||||
int cnt = cmd.getCount();
|
||||
if (raw == 0) {
|
||||
int lines = EditorHelper.getScreenHeight(editor);
|
||||
|
||||
return VimPlugin.getMotion().scrollLine(editor, -lines);
|
||||
final MotionGroup motion = VimPlugin.getMotion();
|
||||
|
||||
int line = cmd.getRawCount();
|
||||
if (line == 0) {
|
||||
final int prevVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor) - 1;
|
||||
line = EditorHelper.visualLineToLogicalLine(editor, prevVisualLine) + 1; // rawCount is 1 based
|
||||
return motion.scrollLineToLastScreenLine(editor, line, true);
|
||||
}
|
||||
else {
|
||||
return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, raw, cnt, true);
|
||||
|
||||
// [count]z^ first scrolls [count] to the bottom of the window, then moves the caret to the line that is now at
|
||||
// the top, and then move that line to the bottom of the window
|
||||
line = EditorHelper.normalizeLine(editor, line);
|
||||
if (motion.scrollLineToLastScreenLine(editor, line, true)) {
|
||||
|
||||
line = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
line = EditorHelper.visualLineToLogicalLine(editor, line) + 1; // rawCount is 1 based
|
||||
return motion.scrollLineToLastScreenLine(editor, line, true);
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollLastScreenLineStartAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.getRawCount(), cmd.getCount(), true);
|
||||
return VimPlugin.getMotion().scrollLineToLastScreenLine(editor, cmd.getRawCount(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollMiddleScreenLineAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.getRawCount(), cmd.getCount(), false);
|
||||
return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.getRawCount(), false);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -35,7 +35,7 @@ public class MotionScrollMiddleScreenLineStartAction extends EditorAction {
|
||||
|
||||
private static class Handler extends EditorActionHandlerBase {
|
||||
protected boolean execute(@NotNull Editor editor, @NotNull DataContext context, @NotNull Command cmd) {
|
||||
return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.getRawCount(), cmd.getCount(), true);
|
||||
return VimPlugin.getMotion().scrollLineToMiddleScreenLine(editor, cmd.getRawCount(), true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@@ -54,7 +54,7 @@ public class WindowDownAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||
return parseKeysSet("<C-W>j");
|
||||
return parseKeysSet("<C-W>j", "<C-W><Down>");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@@ -54,7 +54,7 @@ public class WindowLeftAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||
return parseKeysSet("<C-W>h");
|
||||
return parseKeysSet("<C-W>h", "<C-W><Left>");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@@ -54,7 +54,7 @@ public class WindowRightAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||
return parseKeysSet("<C-W>l");
|
||||
return parseKeysSet("<C-W>l", "<C-W><Right>");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@@ -54,7 +54,7 @@ public class WindowUpAction extends VimCommandAction {
|
||||
@NotNull
|
||||
@Override
|
||||
public Set<List<KeyStroke>> getKeyStrokesSet() {
|
||||
return parseKeysSet("<C-W>k");
|
||||
return parseKeysSet("<C-W>k", "<C-W><Up>");
|
||||
}
|
||||
|
||||
@NotNull
|
||||
|
@@ -90,6 +90,11 @@ public class CommandState {
|
||||
return state.getMode() == Mode.VISUAL && state.getSubMode() == SubMode.VISUAL_BLOCK;
|
||||
}
|
||||
|
||||
public static boolean inSingleCommandMode(@Nullable Editor editor) {
|
||||
final CommandState state = getInstance(editor);
|
||||
return state.getMode() == Mode.COMMAND && state.getSubMode() == SubMode.SINGLE_COMMAND;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public Command getCommand() {
|
||||
return myCommand;
|
||||
|
@@ -34,6 +34,7 @@ import com.intellij.openapi.editor.actionSystem.ActionPlan;
|
||||
import com.intellij.openapi.editor.actionSystem.TypedActionHandler;
|
||||
import com.intellij.openapi.editor.actionSystem.TypedActionHandlerEx;
|
||||
import com.intellij.openapi.editor.event.*;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.editor.impl.TextRangeInterval;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
@@ -43,6 +44,7 @@ import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.intellij.openapi.vfs.VirtualFile;
|
||||
import com.intellij.psi.codeStyle.CodeStyleSettings;
|
||||
import com.intellij.util.ArrayUtil;
|
||||
import com.intellij.util.ObjectUtils;
|
||||
import com.maddyhome.idea.vim.EventFacade;
|
||||
import com.maddyhome.idea.vim.KeyHandler;
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
@@ -66,7 +68,6 @@ import java.util.List;
|
||||
|
||||
/**
|
||||
* Provides all the insert/replace related functionality
|
||||
* TODO - change cursor for the different modes
|
||||
*/
|
||||
public class ChangeGroup {
|
||||
|
||||
@@ -424,7 +425,7 @@ public class ChangeGroup {
|
||||
final Command cmd = state.getCommand();
|
||||
if (cmd != null && state.getMode() == CommandState.Mode.REPEAT) {
|
||||
if (mode == CommandState.Mode.REPLACE) {
|
||||
processInsert(editor, context);
|
||||
setInsertEditorState(editor, false);
|
||||
}
|
||||
if ((cmd.getFlags() & Command.FLAG_NO_REPEAT) != 0) {
|
||||
repeatInsert(editor, context, 1, false);
|
||||
@@ -433,7 +434,7 @@ public class ChangeGroup {
|
||||
repeatInsert(editor, context, cmd.getCount(), false);
|
||||
}
|
||||
if (mode == CommandState.Mode.REPLACE) {
|
||||
processInsert(editor, context);
|
||||
setInsertEditorState(editor, true);
|
||||
}
|
||||
}
|
||||
else {
|
||||
@@ -448,10 +449,7 @@ public class ChangeGroup {
|
||||
documentListener = new InsertActionsDocumentListener();
|
||||
eventFacade.addDocumentListener(document, documentListener);
|
||||
oldOffset = -1;
|
||||
inInsert = true;
|
||||
if (mode == CommandState.Mode.REPLACE) {
|
||||
processInsert(editor, context);
|
||||
}
|
||||
setInsertEditorState(editor, mode == CommandState.Mode.INSERT);
|
||||
state.pushState(mode, CommandState.SubMode.NONE, MappingMode.INSERT);
|
||||
|
||||
resetCursor(editor, true);
|
||||
@@ -468,7 +466,7 @@ public class ChangeGroup {
|
||||
public void processPostChangeModeSwitch(@NotNull Editor editor, @NotNull DataContext context,
|
||||
@NotNull CommandState.Mode toSwitch) {
|
||||
if (toSwitch == CommandState.Mode.INSERT) {
|
||||
initInsert(editor, context, toSwitch);
|
||||
initInsert(editor, context, CommandState.Mode.INSERT);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -617,7 +615,7 @@ public class ChangeGroup {
|
||||
public void processEscape(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
int cnt = lastInsert != null ? lastInsert.getCount() : 0;
|
||||
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.REPLACE) {
|
||||
KeyHandler.executeAction("VimInsertReplaceToggle", context);
|
||||
setInsertEditorState(editor, true);
|
||||
}
|
||||
|
||||
if (lastInsert != null && (lastInsert.getFlags() & Command.FLAG_NO_REPEAT) != 0) {
|
||||
@@ -639,6 +637,7 @@ public class ChangeGroup {
|
||||
markGroup.setMark(editor, MarkGroup.MARK_CHANGE_END, offset);
|
||||
markGroup.setMark(editor, MarkGroup.MARK_CHANGE_POS, offset);
|
||||
CommandState.getInstance(editor).popState();
|
||||
exitAllSingleCommandInsertModes(editor);
|
||||
|
||||
if (!CommandState.inInsertMode(editor)) {
|
||||
resetCursor(editor, false);
|
||||
@@ -656,7 +655,7 @@ public class ChangeGroup {
|
||||
*/
|
||||
public void processEnter(@NotNull Editor editor, @NotNull DataContext context) {
|
||||
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.REPLACE) {
|
||||
KeyHandler.executeAction("EditorToggleInsertState", context);
|
||||
setInsertEditorState(editor, true);
|
||||
}
|
||||
final KeyStroke enterKeyStroke = KeyStroke.getKeyStroke(KeyEvent.VK_ENTER, 0);
|
||||
final List<AnAction> actions = VimPlugin.getKey().getActions(editor.getComponent(), enterKeyStroke);
|
||||
@@ -666,7 +665,7 @@ public class ChangeGroup {
|
||||
}
|
||||
}
|
||||
if (CommandState.getInstance(editor).getMode() == CommandState.Mode.REPLACE) {
|
||||
KeyHandler.executeAction("EditorToggleInsertState", context);
|
||||
setInsertEditorState(editor, false);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -675,12 +674,21 @@ public class ChangeGroup {
|
||||
* Insert/Overwrite state which updates the status bar.
|
||||
*
|
||||
* @param editor The editor to toggle the state in
|
||||
* @param context The data context
|
||||
*/
|
||||
public void processInsert(Editor editor, @NotNull DataContext context) {
|
||||
KeyHandler.executeAction("EditorToggleInsertState", context);
|
||||
public void processInsert(Editor editor) {
|
||||
final EditorEx editorEx = ObjectUtils.tryCast(editor, EditorEx.class);
|
||||
if (editorEx == null) return;
|
||||
editorEx.setInsertMode(!editorEx.isInsertMode());
|
||||
CommandState.getInstance(editor).toggleInsertOverwrite();
|
||||
inInsert = !inInsert;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the insert/replace state of the editor.
|
||||
*/
|
||||
private void setInsertEditorState(@NotNull Editor editor, boolean value) {
|
||||
final EditorEx editorEx = ObjectUtils.tryCast(editor, EditorEx.class);
|
||||
if (editorEx == null) return;
|
||||
editorEx.setInsertMode(value);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -1893,12 +1901,20 @@ public class ChangeGroup {
|
||||
}
|
||||
}
|
||||
|
||||
private void exitAllSingleCommandInsertModes(@NotNull Editor editor) {
|
||||
while (CommandState.inSingleCommandMode(editor)) {
|
||||
CommandState.getInstance(editor).popState();
|
||||
if (CommandState.inInsertMode(editor)) {
|
||||
CommandState.getInstance(editor).popState();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private final List<Object> strokes = new ArrayList<>();
|
||||
private int repeatCharsCount;
|
||||
private List<Object> lastStrokes;
|
||||
@Nullable
|
||||
private Command lastInsert;
|
||||
private boolean inInsert;
|
||||
private int repeatLines;
|
||||
private int repeatColumn;
|
||||
private boolean repeatAppend;
|
||||
|
@@ -55,6 +55,7 @@ import com.maddyhome.idea.vim.ui.ExEntryPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseEvent;
|
||||
import java.io.File;
|
||||
|
||||
@@ -72,7 +73,7 @@ public class MotionGroup {
|
||||
* Create the group
|
||||
*/
|
||||
public MotionGroup() {
|
||||
EventFacade.getInstance().addEditorFactoryListener(new EditorFactoryAdapter() {
|
||||
EventFacade.getInstance().addEditorFactoryListener(new EditorFactoryListener() {
|
||||
public void editorCreated(@NotNull EditorFactoryEvent event) {
|
||||
final Editor editor = event.getEditor();
|
||||
// This ridiculous code ensures that a lot of events are processed BEFORE we finally start listening
|
||||
@@ -760,20 +761,20 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public boolean scrollLineToFirstScreenLine(@NotNull Editor editor, int rawCount, int count, boolean start) {
|
||||
scrollLineToScreenLine(editor, 1, rawCount, count, start);
|
||||
public boolean scrollLineToFirstScreenLine(@NotNull Editor editor, int rawCount, boolean start) {
|
||||
scrollLineToScreenLocation(editor, ScreenLocation.TOP, rawCount, start);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean scrollLineToMiddleScreenLine(@NotNull Editor editor, int rawCount, int count, boolean start) {
|
||||
scrollLineToScreenLine(editor, EditorHelper.getScreenHeight(editor) / 2 + 1, rawCount, count, start);
|
||||
public boolean scrollLineToMiddleScreenLine(@NotNull Editor editor, int rawCount, boolean start) {
|
||||
scrollLineToScreenLocation(editor, ScreenLocation.MIDDLE, rawCount, start);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public boolean scrollLineToLastScreenLine(@NotNull Editor editor, int rawCount, int count, boolean start) {
|
||||
scrollLineToScreenLine(editor, EditorHelper.getScreenHeight(editor), rawCount, count, start);
|
||||
public boolean scrollLineToLastScreenLine(@NotNull Editor editor, int rawCount, boolean start) {
|
||||
scrollLineToScreenLocation(editor, ScreenLocation.BOTTOM, rawCount, start);
|
||||
|
||||
return true;
|
||||
}
|
||||
@@ -813,27 +814,30 @@ public class MotionGroup {
|
||||
false));
|
||||
}
|
||||
|
||||
private void scrollLineToScreenLine(@NotNull Editor editor, int line, int rawCount, int count, boolean start) {
|
||||
int scrollOffset = ((NumberOption) Options.getInstance().getOption("scrolloff")).value();
|
||||
int height = EditorHelper.getScreenHeight(editor);
|
||||
if (scrollOffset > height / 2) {
|
||||
scrollOffset = height / 2;
|
||||
}
|
||||
if (line <= height / 2) {
|
||||
if (line < scrollOffset + 1) {
|
||||
line = scrollOffset + 1;
|
||||
}
|
||||
}
|
||||
else {
|
||||
if (line > height - scrollOffset) {
|
||||
line = height - scrollOffset;
|
||||
}
|
||||
}
|
||||
// Scrolls current or [count] line to given screen location
|
||||
// In Vim, [count] refers to a file line, so it's a logical line
|
||||
private void scrollLineToScreenLocation(@NotNull Editor editor, @NotNull ScreenLocation screenLocation, int line,
|
||||
boolean start) {
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
|
||||
int visualLine = rawCount == 0
|
||||
line = EditorHelper.normalizeLine(editor, line);
|
||||
int visualLine = line == 0
|
||||
? editor.getCaretModel().getVisualPosition().line
|
||||
: EditorHelper.logicalLineToVisualLine(editor, count - 1);
|
||||
scrollLineToTopOfScreen(editor, EditorHelper.normalizeVisualLine(editor, visualLine - line + 1));
|
||||
: EditorHelper.logicalLineToVisualLine(editor, line - 1);
|
||||
|
||||
// This method moves the current (or [count]) line to the specified screen location
|
||||
// Scroll offset is applicable, but scroll jump isn't. Offset is applied to screen lines (visual lines)
|
||||
switch (screenLocation) {
|
||||
case TOP:
|
||||
EditorHelper.scrollVisualLineToTopOfScreen(editor, visualLine - scrollOffset);
|
||||
break;
|
||||
case MIDDLE:
|
||||
EditorHelper.scrollVisualLineToMiddleOfScreen(editor, visualLine);
|
||||
break;
|
||||
case BOTTOM:
|
||||
EditorHelper.scrollVisualLineToBottomOfScreen(editor, visualLine + scrollOffset);
|
||||
break;
|
||||
}
|
||||
if (visualLine != editor.getCaretModel().getVisualPosition().line || start) {
|
||||
int offset;
|
||||
if (start) {
|
||||
@@ -850,51 +854,46 @@ public class MotionGroup {
|
||||
}
|
||||
|
||||
public int moveCaretToFirstScreenLine(@NotNull Editor editor, int count) {
|
||||
return moveCaretToScreenLine(editor, count);
|
||||
return moveCaretToScreenLocation(editor, ScreenLocation.TOP, count);
|
||||
}
|
||||
|
||||
public int moveCaretToLastScreenLine(@NotNull Editor editor, int count) {
|
||||
return moveCaretToScreenLine(editor, EditorHelper.getScreenHeight(editor) - count + 1);
|
||||
return moveCaretToScreenLocation(editor, ScreenLocation.BOTTOM, count);
|
||||
}
|
||||
|
||||
public int moveCaretToMiddleScreenLine(@NotNull Editor editor) {
|
||||
return moveCaretToScreenLine(editor, EditorHelper.getScreenHeight(editor) / 2 + 1);
|
||||
return moveCaretToScreenLocation(editor, ScreenLocation.MIDDLE, 0);
|
||||
}
|
||||
|
||||
private int moveCaretToScreenLine(@NotNull Editor editor, int line) {
|
||||
//saveJumpLocation(editor, context);
|
||||
int scrollOffset = ((NumberOption) Options.getInstance().getOption("scrolloff")).value();
|
||||
int height = EditorHelper.getScreenHeight(editor);
|
||||
if (scrollOffset > height / 2) {
|
||||
scrollOffset = height / 2;
|
||||
// [count] is a visual line offset, which means it's 1 based. The value is ignored for ScreenLocation.MIDDLE
|
||||
private int moveCaretToScreenLocation(@NotNull Editor editor, @NotNull ScreenLocation screenLocation,
|
||||
int visualLineOffset) {
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
|
||||
int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor);
|
||||
|
||||
// Don't apply scrolloff if we're at the top or bottom of the file
|
||||
int offsetTopVisualLine = topVisualLine > 0 ? topVisualLine + scrollOffset : topVisualLine;
|
||||
int offsetBottomVisualLine = bottomVisualLine < EditorHelper.getVisualLineCount(editor) ? bottomVisualLine - scrollOffset : bottomVisualLine;
|
||||
|
||||
// [count]H/[count]L moves caret to that screen line, bounded by top/bottom scroll offsets
|
||||
int targetVisualLine = 0;
|
||||
switch (screenLocation) {
|
||||
case TOP:
|
||||
targetVisualLine = Math.max(offsetTopVisualLine, topVisualLine + visualLineOffset - 1);
|
||||
targetVisualLine = Math.min(targetVisualLine, offsetBottomVisualLine);
|
||||
break;
|
||||
case MIDDLE:
|
||||
targetVisualLine = EditorHelper.getVisualLineAtMiddleOfScreen(editor);
|
||||
break;
|
||||
case BOTTOM:
|
||||
targetVisualLine = Math.min(offsetBottomVisualLine, bottomVisualLine - visualLineOffset + 1);
|
||||
targetVisualLine = Math.max(targetVisualLine, offsetTopVisualLine);
|
||||
break;
|
||||
}
|
||||
|
||||
int top = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
|
||||
if (line > height - scrollOffset && top < EditorHelper.getLineCount(editor) - height) {
|
||||
line = height - scrollOffset;
|
||||
}
|
||||
else if (line <= scrollOffset && top > 0) {
|
||||
line = scrollOffset + 1;
|
||||
}
|
||||
|
||||
return moveCaretToLineStartSkipLeading(editor, EditorHelper.visualLineToLogicalLine(editor, top + line - 1));
|
||||
}
|
||||
|
||||
public boolean scrollHalfPage(@NotNull Editor editor, int dir, int count) {
|
||||
NumberOption scroll = (NumberOption) Options.getInstance().getOption("scroll");
|
||||
int height = EditorHelper.getScreenHeight(editor) / 2;
|
||||
if (count == 0) {
|
||||
count = scroll.value();
|
||||
if (count == 0) {
|
||||
count = height;
|
||||
}
|
||||
}
|
||||
else {
|
||||
scroll.set(count);
|
||||
}
|
||||
|
||||
return scrollPage(editor, dir, count, EditorHelper.getCurrentVisualScreenLine(editor), true);
|
||||
return moveCaretToLineStartSkipLeading(editor, EditorHelper.visualLineToLogicalLine(editor, targetVisualLine));
|
||||
}
|
||||
|
||||
public boolean scrollColumn(@NotNull Editor editor, int columns) {
|
||||
@@ -913,7 +912,7 @@ public class MotionGroup {
|
||||
int visualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
|
||||
visualLine = EditorHelper.normalizeVisualLine(editor, visualLine + lines);
|
||||
scrollLineToTopOfScreen(editor, visualLine);
|
||||
EditorHelper.scrollVisualLineToTopOfScreen(editor, visualLine);
|
||||
|
||||
moveCaretToView(editor);
|
||||
|
||||
@@ -921,25 +920,23 @@ public class MotionGroup {
|
||||
}
|
||||
|
||||
private static void moveCaretToView(@NotNull Editor editor) {
|
||||
int scrollOffset = ((NumberOption) Options.getInstance().getOption("scrolloff")).value();
|
||||
int sideScrollOffset = ((NumberOption) Options.getInstance().getOption("sidescrolloff")).value();
|
||||
int height = EditorHelper.getScreenHeight(editor);
|
||||
int width = EditorHelper.getScreenWidth(editor);
|
||||
if (scrollOffset > height / 2) {
|
||||
scrollOffset = height / 2;
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
|
||||
int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor);
|
||||
int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
|
||||
int newline = caretVisualLine;
|
||||
if (caretVisualLine < topVisualLine + scrollOffset) {
|
||||
newline = EditorHelper.normalizeVisualLine(editor, topVisualLine + scrollOffset);
|
||||
}
|
||||
if (sideScrollOffset > width / 2) {
|
||||
sideScrollOffset = width / 2;
|
||||
else if (caretVisualLine >= bottomVisualLine - scrollOffset) {
|
||||
newline = EditorHelper.normalizeVisualLine(editor, bottomVisualLine - scrollOffset);
|
||||
}
|
||||
|
||||
int visualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
int cline = editor.getCaretModel().getVisualPosition().line;
|
||||
int newline = cline;
|
||||
if (cline < visualLine + scrollOffset) {
|
||||
newline = EditorHelper.normalizeVisualLine(editor, visualLine + scrollOffset);
|
||||
}
|
||||
else if (cline >= visualLine + height - scrollOffset) {
|
||||
newline = EditorHelper.normalizeVisualLine(editor, visualLine + height - scrollOffset - 1);
|
||||
int sideScrollOffset = ((NumberOption) Options.getInstance().getOption("sidescrolloff")).value();
|
||||
int width = EditorHelper.getScreenWidth(editor);
|
||||
if (sideScrollOffset > width / 2) {
|
||||
sideScrollOffset = width / 2;
|
||||
}
|
||||
|
||||
int col = editor.getCaretModel().getVisualPosition().column;
|
||||
@@ -957,13 +954,13 @@ public class MotionGroup {
|
||||
newColumn = visualColumn + width - sideScrollOffset - 1;
|
||||
}
|
||||
|
||||
if (newline == cline && newColumn != caretColumn) {
|
||||
if (newline == caretVisualLine && newColumn != caretColumn) {
|
||||
col = newColumn;
|
||||
}
|
||||
|
||||
newColumn = EditorHelper.normalizeVisualColumn(editor, newline, newColumn, CommandState.inInsertMode(editor));
|
||||
|
||||
if (newline != cline || newColumn != oldColumn) {
|
||||
if (newline != caretVisualLine || newColumn != oldColumn) {
|
||||
int offset = EditorHelper.visualPositionToOffset(editor, new VisualPosition(newline, newColumn));
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), offset);
|
||||
|
||||
@@ -972,61 +969,116 @@ public class MotionGroup {
|
||||
}
|
||||
|
||||
public boolean scrollFullPage(@NotNull Editor editor, int pages) {
|
||||
int height = EditorHelper.getScreenHeight(editor);
|
||||
int line = pages > 0 ? 1 : height;
|
||||
int caretVisualLine = EditorHelper.scrollFullPage(editor, pages);
|
||||
if (caretVisualLine != -1) {
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
boolean success = true;
|
||||
|
||||
return scrollPage(editor, pages, height - 2, line, false);
|
||||
if (pages > 0) {
|
||||
// If the caret is ending up passed the end of the file, we need to beep
|
||||
if (caretVisualLine > EditorHelper.getVisualLineCount(editor) - 1) {
|
||||
success = false;
|
||||
}
|
||||
|
||||
int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
if (caretVisualLine < topVisualLine + scrollOffset) {
|
||||
caretVisualLine = EditorHelper.normalizeVisualLine(editor, caretVisualLine + scrollOffset);
|
||||
}
|
||||
}
|
||||
else if (pages < 0) {
|
||||
int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen( editor);
|
||||
if (caretVisualLine > bottomVisualLine - scrollOffset) {
|
||||
caretVisualLine = EditorHelper.normalizeVisualLine(editor, caretVisualLine - scrollOffset);
|
||||
}
|
||||
}
|
||||
|
||||
int offset = moveCaretToLineStartSkipLeading(editor, EditorHelper.visualLineToLogicalLine(editor, caretVisualLine));
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), offset);
|
||||
return success;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean scrollPage(@NotNull Editor editor, int pages, int height, int line, boolean partial) {
|
||||
int visualTopLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
public boolean scrollScreen(@NotNull final Editor editor, int rawCount, boolean down) {
|
||||
final CaretModel caretModel = editor.getCaretModel();
|
||||
final int currentLogicalLine = caretModel.getLogicalPosition().line;
|
||||
|
||||
int newLine = visualTopLine + pages * height;
|
||||
int topLine = EditorHelper.normalizeVisualLine(editor, newLine);
|
||||
|
||||
boolean moved = scrollLineToTopOfScreen(editor, topLine);
|
||||
visualTopLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
|
||||
if (moved && topLine == newLine && topLine == visualTopLine) {
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), moveCaretToScreenLine(editor, line));
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (moved && !partial) {
|
||||
int visualLine = Math.abs(visualTopLine - newLine) % height + 1;
|
||||
if (pages < 0) {
|
||||
visualLine = height - visualLine + 3;
|
||||
}
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), moveCaretToScreenLine(editor, visualLine));
|
||||
|
||||
return true;
|
||||
}
|
||||
else if (partial) {
|
||||
int cline = editor.getCaretModel().getVisualPosition().line;
|
||||
int visualLine = cline + pages * height;
|
||||
visualLine = EditorHelper.normalizeVisualLine(editor, visualLine);
|
||||
if (cline == visualLine) {
|
||||
return false;
|
||||
}
|
||||
|
||||
int logicalLine = editor.visualToLogicalPosition(new VisualPosition(visualLine, 0)).line;
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), moveCaretToLineStartSkipLeading(editor, logicalLine));
|
||||
|
||||
return true;
|
||||
}
|
||||
else {
|
||||
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(),
|
||||
moveCaretToLineStartSkipLeading(editor, editor.getCaretModel().getPrimaryCaret()));
|
||||
if ((!down && currentLogicalLine <= 0) || (down && currentLogicalLine >= EditorHelper.getLineCount(editor) - 1)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
final Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
|
||||
int targetCaretVisualLine = getScrollScreenTargetCaretVisualLine(editor, rawCount, down);
|
||||
|
||||
// Scroll at most one screen height
|
||||
final int yInitialCaret = editor.visualLineToY(caretModel.getVisualPosition().line);
|
||||
final int yTargetVisualLine = editor.visualLineToY(targetCaretVisualLine);
|
||||
if (Math.abs(yTargetVisualLine - yInitialCaret) > visibleArea.height) {
|
||||
|
||||
final int yPrevious = visibleArea.y;
|
||||
boolean moved;
|
||||
if (down) {
|
||||
targetCaretVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor) + 1;
|
||||
moved = EditorHelper.scrollVisualLineToTopOfScreen(editor, targetCaretVisualLine);
|
||||
} else {
|
||||
targetCaretVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor) - 1;
|
||||
moved = EditorHelper.scrollVisualLineToBottomOfScreen(editor, targetCaretVisualLine);
|
||||
}
|
||||
if (moved) {
|
||||
// We'll keep the caret at the same position, although that might not be the same line offset as previously
|
||||
targetCaretVisualLine = editor.yToVisualLine(yInitialCaret + scrollingModel.getVisibleArea().y - yPrevious);
|
||||
}
|
||||
} else {
|
||||
|
||||
EditorHelper.scrollVisualLineToCaretLocation(editor, targetCaretVisualLine);
|
||||
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
final int visualTop = EditorHelper.getVisualLineAtTopOfScreen(editor) + scrollOffset;
|
||||
final int visualBottom = EditorHelper.getVisualLineAtBottomOfScreen(editor) - scrollOffset;
|
||||
|
||||
targetCaretVisualLine = Math.max(visualTop, Math.min(visualBottom, targetCaretVisualLine));
|
||||
}
|
||||
|
||||
int logicalLine = EditorHelper.visualLineToLogicalLine(editor, targetCaretVisualLine);
|
||||
int caretOffset = moveCaretToLineStartSkipLeading(editor, logicalLine);
|
||||
moveCaret(editor, caretModel.getPrimaryCaret(), caretOffset);
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static boolean scrollLineToTopOfScreen(@NotNull Editor editor, int line) {
|
||||
int pos = line * editor.getLineHeight();
|
||||
int verticalPos = editor.getScrollingModel().getVerticalScrollOffset();
|
||||
editor.getScrollingModel().scrollVertically(pos);
|
||||
private static int getScrollScreenTargetCaretVisualLine(@NotNull final Editor editor, int rawCount, boolean down) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int caretVisualLine = editor.getCaretModel().getVisualPosition().line;
|
||||
final int scrollOption = getScrollOption(rawCount);
|
||||
|
||||
return verticalPos != editor.getScrollingModel().getVerticalScrollOffset();
|
||||
int targetCaretVisualLine;
|
||||
if (scrollOption == 0) {
|
||||
// Scroll up/down half window size by default. We can't use line count here because of block inlays
|
||||
final int offset = down ? (visibleArea.height / 2) : editor.getLineHeight() - (visibleArea.height / 2);
|
||||
targetCaretVisualLine = editor.yToVisualLine(editor.visualLineToY(caretVisualLine) + offset);
|
||||
} else {
|
||||
targetCaretVisualLine = down ? caretVisualLine + scrollOption : caretVisualLine - scrollOption;
|
||||
}
|
||||
|
||||
return targetCaretVisualLine;
|
||||
}
|
||||
|
||||
private static int getScrollOption(int rawCount) {
|
||||
NumberOption scroll = (NumberOption) Options.getInstance().getOption("scroll");
|
||||
if (rawCount == 0) {
|
||||
return scroll.value();
|
||||
}
|
||||
// TODO: This needs to be reset whenever the window size changes
|
||||
scroll.set(rawCount);
|
||||
return rawCount;
|
||||
}
|
||||
|
||||
private static int getNormalizedScrollOffset(@NotNull final Editor editor) {
|
||||
int scrollOffset = ((NumberOption) Options.getInstance().getOption("scrolloff")).value();
|
||||
return EditorHelper.normalizeScrollOffset(editor, scrollOffset);
|
||||
}
|
||||
|
||||
private static void scrollColumnToLeftOfScreen(@NotNull Editor editor, int column) {
|
||||
@@ -1327,53 +1379,55 @@ public class MotionGroup {
|
||||
|
||||
public static void scrollPositionIntoView(@NotNull Editor editor, @NotNull VisualPosition position,
|
||||
boolean scrollJump) {
|
||||
final int line = position.line;
|
||||
final int topVisualLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
final int bottomVisualLine = EditorHelper.getVisualLineAtBottomOfScreen(editor);
|
||||
final int visualLine = position.line;
|
||||
final int column = position.column;
|
||||
final int topLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
|
||||
int scrollOffset = ((NumberOption) Options.getInstance().getOption("scrolloff")).value();
|
||||
|
||||
int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
|
||||
int scrollJumpSize = 0;
|
||||
if (scrollJump) {
|
||||
scrollJumpSize = Math.max(0, ((NumberOption) Options.getInstance().getOption("scrolljump")).value() - 1);
|
||||
}
|
||||
|
||||
int height = EditorHelper.getScreenHeight(editor);
|
||||
int visualTop = topLine + scrollOffset;
|
||||
int visualBottom = topLine + height - scrollOffset;
|
||||
if (scrollOffset >= height / 2) {
|
||||
scrollOffset = height / 2;
|
||||
visualTop = topLine + scrollOffset;
|
||||
visualBottom = topLine + height - scrollOffset;
|
||||
if (visualTop == visualBottom) {
|
||||
visualBottom++;
|
||||
}
|
||||
int visualTop = topVisualLine + scrollOffset;
|
||||
int visualBottom = bottomVisualLine - scrollOffset;
|
||||
if (visualTop == visualBottom) {
|
||||
visualBottom++;
|
||||
}
|
||||
|
||||
int diff;
|
||||
if (line < visualTop) {
|
||||
diff = line - visualTop;
|
||||
if (visualLine < visualTop) {
|
||||
diff = visualLine - visualTop;
|
||||
scrollJumpSize = -scrollJumpSize;
|
||||
}
|
||||
else {
|
||||
diff = line - visualBottom + 1;
|
||||
if (diff < 0) {
|
||||
diff = 0;
|
||||
}
|
||||
} else {
|
||||
diff = Math.max(0, visualLine - visualBottom);
|
||||
}
|
||||
|
||||
if (diff != 0) {
|
||||
int resLine;
|
||||
// If we need to move the top line more than a half screen worth then we just center the cursor line
|
||||
if (Math.abs(diff) > height / 2) {
|
||||
resLine = line - height / 2 - 1;
|
||||
}
|
||||
// Otherwise put the new cursor line "scrolljump" lines from the top/bottom
|
||||
else {
|
||||
resLine = topLine + diff + scrollJumpSize;
|
||||
}
|
||||
|
||||
resLine = Math.min(resLine, EditorHelper.getVisualLineCount(editor) - height);
|
||||
resLine = Math.max(0, resLine);
|
||||
scrollLineToTopOfScreen(editor, resLine);
|
||||
// If we need to move the top line more than a half screen worth then we just center the cursor line.
|
||||
// Block inlays mean that this half screen height isn't a consistent pixel height, and might be larger than line
|
||||
// height multiplied by number of lines, but it's still a good heuristic to use here
|
||||
int height = bottomVisualLine - topVisualLine + 1;
|
||||
if (Math.abs(diff) > height / 2) {
|
||||
EditorHelper.scrollVisualLineToMiddleOfScreen(editor, visualLine);
|
||||
}
|
||||
else {
|
||||
// Put the new cursor line "scrolljump" lines from the top/bottom. Ensure that the line is fully visible,
|
||||
// including block inlays above/below the line
|
||||
if (diff > 0) {
|
||||
int resLine = bottomVisualLine + diff + scrollJumpSize;
|
||||
EditorHelper.scrollVisualLineToBottomOfScreen(editor, resLine);
|
||||
}
|
||||
else {
|
||||
int resLine = topVisualLine + diff + scrollJumpSize;
|
||||
resLine = Math.min(resLine, EditorHelper.getVisualLineCount(editor) - height);
|
||||
resLine = Math.max(0, resLine);
|
||||
EditorHelper.scrollVisualLineToTopOfScreen(editor, resLine);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int visualColumn = EditorHelper.getVisualColumnAtLeftOfScreen(editor);
|
||||
@@ -1856,7 +1910,7 @@ public class MotionGroup {
|
||||
}
|
||||
|
||||
private static class EditorMouseHandler implements EditorMouseListener, EditorMouseMotionListener {
|
||||
public void mouseMoved(EditorMouseEvent event) {
|
||||
public void mouseMoved(@NotNull EditorMouseEvent event) {
|
||||
}
|
||||
|
||||
public void mouseDragged(@NotNull EditorMouseEvent event) {
|
||||
@@ -1879,7 +1933,7 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public void mousePressed(EditorMouseEvent event) {
|
||||
public void mousePressed(@NotNull EditorMouseEvent event) {
|
||||
}
|
||||
|
||||
public void mouseClicked(@NotNull EditorMouseEvent event) {
|
||||
@@ -1905,10 +1959,10 @@ public class MotionGroup {
|
||||
}
|
||||
}
|
||||
|
||||
public void mouseEntered(EditorMouseEvent event) {
|
||||
public void mouseEntered(@NotNull EditorMouseEvent event) {
|
||||
}
|
||||
|
||||
public void mouseExited(EditorMouseEvent event) {
|
||||
public void mouseExited(@NotNull EditorMouseEvent event) {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@@ -1919,6 +1973,12 @@ public class MotionGroup {
|
||||
private int endOff;
|
||||
}
|
||||
|
||||
private enum ScreenLocation {
|
||||
TOP,
|
||||
MIDDLE,
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
public int getLastFTCmd() {
|
||||
return lastFTCmd;
|
||||
}
|
||||
|
@@ -22,6 +22,7 @@ import com.intellij.application.options.CodeStyle;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.actionSystem.PlatformDataKeys;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.impl.EditorImpl;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.FileEditorManager;
|
||||
import com.intellij.openapi.fileTypes.FileType;
|
||||
@@ -47,12 +48,19 @@ import java.util.List;
|
||||
*/
|
||||
public class EditorHelper {
|
||||
public static int getVisualLineAtTopOfScreen(@NotNull final Editor editor) {
|
||||
int lh = editor.getLineHeight();
|
||||
return (editor.getScrollingModel().getVerticalScrollOffset() + lh - 1) / lh;
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
return getFullVisualLine(editor, visibleArea.y, visibleArea.y, visibleArea.y + visibleArea.height);
|
||||
}
|
||||
|
||||
public static int getCurrentVisualScreenLine(@NotNull final Editor editor) {
|
||||
return editor.getCaretModel().getVisualPosition().line - getVisualLineAtTopOfScreen(editor) + 1;
|
||||
public static int getVisualLineAtMiddleOfScreen(@NotNull final Editor editor) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
final Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
return editor.yToVisualLine(visibleArea.y + (visibleArea.height / 2));
|
||||
}
|
||||
|
||||
public static int getVisualLineAtBottomOfScreen(@NotNull final Editor editor) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
return getFullVisualLine(editor, visibleArea.y + visibleArea.height, visibleArea.y, visibleArea.y + visibleArea.height);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -145,14 +153,35 @@ public class EditorHelper {
|
||||
return includeEndNewLine || len == 0 || editor.getDocument().getCharsSequence().charAt(len - 1) != '\n' ? len : len - 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Best efforts to ensure that scroll offset doesn't overlap itself.
|
||||
*
|
||||
* This is a sanity check that works fine if there are no visible block inlays. Otherwise, the screen height depends
|
||||
* on what block inlays are currently visible in the target scroll area. Given a large enough scroll offset (or small
|
||||
* enough screen), we can return a scroll offset that takes us over the half way point and causes scrolling issues -
|
||||
* skipped lines, or unexpected movement.
|
||||
*
|
||||
* TODO: Investigate better ways of handling scroll offset
|
||||
* Perhaps apply scroll offset after the move itself? Calculate a safe offset based on a target area?
|
||||
*
|
||||
* @param editor The editor to use to normalize the scroll offset
|
||||
* @param scrollOffset The value of the 'scrolloff' option
|
||||
* @return The scroll offset value to use
|
||||
*/
|
||||
public static int normalizeScrollOffset(@NotNull final Editor editor, int scrollOffset) {
|
||||
return Math.min(scrollOffset, getApproximateScreenHeight(editor) / 2);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the number of lines than can be displayed on the screen at one time. This is rounded down to the
|
||||
* nearest whole line if there is a partial line visible at the bottom of the screen.
|
||||
*
|
||||
* Note that this value is only approximate and should be avoided whenever possible!
|
||||
*
|
||||
* @param editor The editor
|
||||
* @return The number of screen lines
|
||||
*/
|
||||
public static int getScreenHeight(@NotNull final Editor editor) {
|
||||
private static int getApproximateScreenHeight(@NotNull final Editor editor) {
|
||||
int lh = editor.getLineHeight();
|
||||
int height = editor.getScrollingModel().getVisibleArea().y +
|
||||
editor.getScrollingModel().getVisibleArea().height -
|
||||
@@ -223,6 +252,10 @@ public class EditorHelper {
|
||||
* @return The visual line number
|
||||
*/
|
||||
public static int logicalLineToVisualLine(@NotNull final Editor editor, final int line) {
|
||||
if (editor instanceof EditorImpl) {
|
||||
// This is faster than simply calling Editor#logicalToVisualPosition
|
||||
return ((EditorImpl) editor).offsetToVisualLine(editor.getDocument().getLineStartOffset(line));
|
||||
}
|
||||
return editor.logicalToVisualPosition(new LogicalPosition(line, 0)).line;
|
||||
}
|
||||
|
||||
@@ -589,4 +622,222 @@ public class EditorHelper {
|
||||
|
||||
return carets;
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the editor to put the given visual line at the current caret location, relative to the screen.
|
||||
*
|
||||
* Due to block inlays, the caret location is maintained as a scroll offset, rather than the number of lines from the
|
||||
* top of the screen. This means the line offset can change if the number of inlays above the caret changes during
|
||||
* scrolling. It also means that after scrolling, the top screen line isn't guaranteed to be aligned to the top of
|
||||
* the screen, unlike most other motions ('M' is the only other motion that doesn't align the top line).
|
||||
*
|
||||
* This method will also move the caret location to ensure that any inlays attached above or below the target line are
|
||||
* fully visible.
|
||||
*
|
||||
* @param editor The editor to scroll
|
||||
* @param visualLine The visual line to scroll to the current caret location
|
||||
*/
|
||||
public static void scrollVisualLineToCaretLocation(@NotNull final Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
final Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
final int caretScreenOffset = editor.visualLineToY(editor.getCaretModel().getVisualPosition().line) - visibleArea.y;
|
||||
|
||||
final int yVisualLine = editor.visualLineToY(visualLine);
|
||||
|
||||
// We try to keep the caret in the same location, but only if there's enough space all around for the line's
|
||||
// inlays. E.g. caret on top screen line and the line has inlays above, or caret on bottom screen line and has
|
||||
// inlays below
|
||||
final int topInlayHeight = EditorHelper.getHeightOfVisualLineInlays(editor, visualLine, true);
|
||||
final int bottomInlayHeight = EditorHelper.getHeightOfVisualLineInlays(editor, visualLine, false);
|
||||
|
||||
int inlayOffset = 0;
|
||||
if (topInlayHeight > caretScreenOffset) {
|
||||
inlayOffset = topInlayHeight;
|
||||
} else if (bottomInlayHeight > visibleArea.height - caretScreenOffset + editor.getLineHeight()) {
|
||||
inlayOffset = -bottomInlayHeight;
|
||||
}
|
||||
|
||||
scrollingModel.scrollVertically(yVisualLine - caretScreenOffset - inlayOffset);
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the editor to put the given visual line at the top of the current window. Ensures that any block inlay
|
||||
* elements above the given line are also visible.
|
||||
*
|
||||
* @param editor The editor to scroll
|
||||
* @param visualLine The visual line to place at the top of the current window
|
||||
* @return Returns true if the window was moved
|
||||
*/
|
||||
public static boolean scrollVisualLineToTopOfScreen(@NotNull final Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, true);
|
||||
int y = editor.visualLineToY(visualLine) - inlayHeight;
|
||||
int verticalPos = scrollingModel.getVerticalScrollOffset();
|
||||
scrollingModel.scrollVertically(y);
|
||||
|
||||
return verticalPos != scrollingModel.getVerticalScrollOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the editor to place the given visual line in the middle of the current window.
|
||||
*
|
||||
* @param editor The editor to scroll
|
||||
* @param visualLine The visual line to place in the middle of the current window
|
||||
*/
|
||||
public static void scrollVisualLineToMiddleOfScreen(@NotNull Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
int y = editor.visualLineToY(visualLine);
|
||||
int lineHeight = editor.getLineHeight();
|
||||
int height = scrollingModel.getVisibleArea().height;
|
||||
scrollingModel.scrollVertically(y - ((height - lineHeight) / 2));
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the editor to place the given visual line at the bottom of the screen.
|
||||
*
|
||||
* When we're moving the caret down a few lines and want to scroll to keep this visible, we need to be able to place a
|
||||
* line at the bottom of the screen. Due to block inlays, we can't do this by specifying a top line to scroll to.
|
||||
*
|
||||
* @param editor The editor to scroll
|
||||
* @param visualLine The visual line to place at the bottom of the current window
|
||||
* @return True if the editor was scrolled
|
||||
*/
|
||||
public static boolean scrollVisualLineToBottomOfScreen(@NotNull Editor editor, int visualLine) {
|
||||
final ScrollingModel scrollingModel = editor.getScrollingModel();
|
||||
int inlayHeight = getHeightOfVisualLineInlays(editor, visualLine, false);
|
||||
int y = editor.visualLineToY(visualLine);
|
||||
int verticalPos = scrollingModel.getVerticalScrollOffset();
|
||||
int height = inlayHeight + editor.getLineHeight();
|
||||
|
||||
Rectangle visibleArea = scrollingModel.getVisibleArea();
|
||||
|
||||
// For consistency, we always try to scroll to keep a whole line (with inlays) aligned at the top of the screen.
|
||||
// This is inexact, and means we can bounce around, most visibly when the caret is on the last line and we're moving
|
||||
// down (j) or the caret is on the last line and we're scrolling up (CTRL-Y)
|
||||
// If we want it to be simpler: scrollingModel.scrollVertically(y - visibleArea.height + height);
|
||||
|
||||
int topVisualLine = editor.yToVisualLine(y - visibleArea.height + height);
|
||||
int topLineInlayHeight = getHeightOfVisualLineInlays(editor, topVisualLine, true);
|
||||
int topY = editor.visualLineToY(topVisualLine);
|
||||
if (topY - topLineInlayHeight + visibleArea.height < y + height) {
|
||||
// There's a pathological edge case here, if topVisualLine has a HUGE inlay, then topVisualLine+1 won't put our
|
||||
// given line at the bottom of the screen
|
||||
scrollVisualLineToTopOfScreen(editor, topVisualLine + 1);
|
||||
} else {
|
||||
scrollingModel.scrollVertically(topY - topLineInlayHeight);
|
||||
}
|
||||
|
||||
return verticalPos != scrollingModel.getVerticalScrollOffset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Scrolls the screen up or down one or more pages.
|
||||
*
|
||||
* @param editor The editor to scroll
|
||||
* @param pages The number of pages to scroll. Positive is scroll down (lines move up). Negative is scroll up.
|
||||
* @return The visual line to place the caret on. -1 if the page wasn't scrolled at all.
|
||||
*/
|
||||
public static int scrollFullPage(@NotNull final Editor editor, int pages) {
|
||||
if (pages > 0) {
|
||||
return scrollFullPageDown(editor, pages);
|
||||
}
|
||||
else if (pages < 0) {
|
||||
return scrollFullPageUp(editor, pages);
|
||||
}
|
||||
return -1; // visual lines are 1-based
|
||||
}
|
||||
|
||||
private static int scrollFullPageDown(@NotNull final Editor editor, int pages) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int lineCount = getVisualLineCount(editor);
|
||||
|
||||
if (editor.getCaretModel().getVisualPosition().line == lineCount - 1)
|
||||
return -1;
|
||||
|
||||
int y = visibleArea.y + visibleArea.height;
|
||||
int topBound = visibleArea.y;
|
||||
int bottomBound = visibleArea.y + visibleArea.height;
|
||||
int line = 0;
|
||||
int caretLine = -1;
|
||||
|
||||
for (int i = 0; i < pages; i++) {
|
||||
line = getFullVisualLine(editor, y, topBound, bottomBound);
|
||||
if (line >= lineCount - 1) {
|
||||
// If we're on the last page, end nicely on the last line, otherwise return the overrun so we can "beep"
|
||||
if (i == pages - 1) {
|
||||
caretLine = lineCount - 1;
|
||||
}
|
||||
else {
|
||||
caretLine = line;
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
// The help page for 'scrolling' states that a page is the number of lines in the window minus two. Scrolling a
|
||||
// page adds this page length to the current line. Or in other words, scrolling down a page puts the last but one
|
||||
// line at the top of the next page.
|
||||
// E.g. a window showing lines 1-35 has a page size of 33, and scrolling down a page shows 34 as the top line
|
||||
line--;
|
||||
|
||||
y = editor.visualLineToY(line);
|
||||
topBound = y;
|
||||
bottomBound = y + visibleArea.height;
|
||||
y = bottomBound;
|
||||
caretLine = line;
|
||||
}
|
||||
|
||||
scrollVisualLineToTopOfScreen(editor, line);
|
||||
return caretLine;
|
||||
}
|
||||
|
||||
private static int scrollFullPageUp(@NotNull final Editor editor, int pages) {
|
||||
final Rectangle visibleArea = editor.getScrollingModel().getVisibleArea();
|
||||
final int lineHeight = editor.getLineHeight();
|
||||
|
||||
int y = visibleArea.y;
|
||||
int topBound = visibleArea.y;
|
||||
int bottomBound = visibleArea.y + visibleArea.height;
|
||||
int line = 0;
|
||||
int caretLine = -1;
|
||||
|
||||
// We know pages is negative
|
||||
for (int i = pages; i < 0; i++) {
|
||||
// E.g. a window showing 73-107 has page size 33. Scrolling up puts 74 at the bottom of the screen
|
||||
line = getFullVisualLine(editor, y, topBound, bottomBound) + 1;
|
||||
if (line == 1) {
|
||||
break;
|
||||
}
|
||||
|
||||
y = editor.visualLineToY(line);
|
||||
bottomBound = y + lineHeight;
|
||||
topBound = bottomBound - visibleArea.height;
|
||||
y = topBound;
|
||||
caretLine = line;
|
||||
}
|
||||
|
||||
scrollVisualLineToBottomOfScreen(editor, line);
|
||||
return caretLine;
|
||||
}
|
||||
|
||||
private static int getFullVisualLine(@NotNull final Editor editor, int y, int topBound, int bottomBound) {
|
||||
int line = editor.yToVisualLine(y);
|
||||
int yActual = editor.visualLineToY(line);
|
||||
if (yActual < topBound) {
|
||||
line++;
|
||||
}
|
||||
else if (yActual + editor.getLineHeight() > bottomBound) {
|
||||
line--;
|
||||
}
|
||||
return line;
|
||||
}
|
||||
|
||||
private static int getHeightOfVisualLineInlays(@NotNull final Editor editor, int visualLine, boolean above) {
|
||||
InlayModel inlayModel = editor.getInlayModel();
|
||||
List<Inlay> inlays = inlayModel.getBlockElementsForVisualLine(visualLine, above);
|
||||
int inlayHeight = 0;
|
||||
for (Inlay inlay : inlays) {
|
||||
inlayHeight += inlay.getHeightInPixels();
|
||||
}
|
||||
return inlayHeight;
|
||||
}
|
||||
}
|
||||
|
@@ -1,6 +1,7 @@
|
||||
package org.jetbrains.plugins.ideavim.action;
|
||||
|
||||
import com.maddyhome.idea.vim.VimPlugin;
|
||||
import com.maddyhome.idea.vim.command.CommandState;
|
||||
import org.jetbrains.plugins.ideavim.VimTestCase;
|
||||
|
||||
import static com.maddyhome.idea.vim.helper.StringHelper.parseKeys;
|
||||
@@ -29,6 +30,20 @@ public class ChangeActionTest extends VimTestCase {
|
||||
"}\n");
|
||||
}
|
||||
|
||||
// VIM-620 |i_CTRL-O|
|
||||
public void testInsertSingleCommandAndInserting() {
|
||||
doTest(parseKeys("i", "<C-O>", "a", "123", "<Esc>", "x"),
|
||||
"abc<caret>d\n",
|
||||
"abcd12\n");
|
||||
}
|
||||
|
||||
// VIM-620 |i_CTRL-O|
|
||||
public void testInsertSingleCommandAndNewLineInserting() {
|
||||
doTest(parseKeys("i", "<C-O>", "o", "123", "<Esc>", "x"),
|
||||
"abc<caret>d\n",
|
||||
"abcd\n12\n");
|
||||
}
|
||||
|
||||
// VIM-311 |i_CTRL-O|
|
||||
public void testInsertSingleCommand() {
|
||||
doTest(parseKeys("i", "def", "<C-O>", "d2h", "x"),
|
||||
@@ -645,4 +660,13 @@ public class ChangeActionTest extends VimTestCase {
|
||||
"<caret>Xfoo\n" +
|
||||
"Xbar\n");
|
||||
}
|
||||
|
||||
public void testRepeatReplace() {
|
||||
configureByText("<caret>foobarbaz spam\n");
|
||||
typeText(parseKeys("R"));
|
||||
assertMode(CommandState.Mode.REPLACE);
|
||||
typeText(parseKeys("FOO", "<Esc>", "l", "2."));
|
||||
myFixture.checkResult("FOOFOOFO<caret>O spam\n");
|
||||
assertMode(CommandState.Mode.COMMAND);
|
||||
}
|
||||
}
|
||||
|
Reference in New Issue
Block a user