1
0
mirror of https://github.com/chylex/IntelliJ-IdeaVim.git synced 2025-08-17 16:31:45 +02:00

Compare commits

...

25 Commits
0.50 ... 0.51

Author SHA1 Message Date
Andrey Vlasovskikh
132f52785c Version 0.51 2019-02-12 00:08:12 +03:00
Andrey Vlasovskikh
c556ec2001 Merge branch 'fixes-by-matt'
# Conflicts:
#	resources/META-INF/plugin.xml
2019-01-28 23:05:23 +03:00
Andrey Vlasovskikh
d49683ab2f Updated Kotlin to 1.3 due to compatibility IntelliJ 2019.1
The minimal compatible version is now 2018.3.
2019-01-28 22:56:14 +03:00
Andrey Vlasovskikh
810c3cd561 Use the IntelliJ repo instead of the default cache-redirector due to network issues 2019-01-28 21:54:55 +03:00
Andrey Vlasovskikh
b909157f4b Mention Rider as a compatible IDE 2019-01-28 21:51:16 +03:00
Andrey Vlasovskikh
21c1232ba6 Updated the changelog 2019-01-28 21:42:21 +03:00
Andrey Vlasovskikh
ff61a42670 Updated usages of obsolete APIs 2019-01-28 21:40:14 +03:00
Andrey Vlasovskikh
f160d855c0 Added missing nullity annotations 2019-01-28 21:38:37 +03:00
Andrey Vlasovskikh
51685a2094 Renamed attributes according to naming convention 2019-01-28 20:49:23 +03:00
Andrey Vlasovskikh
487c71ec15 Updated since-build according to the version that introduced the required API 2019-01-28 20:42:40 +03:00
Andrey Vlasovskikh
39aa60850d Fixed inspection warnings 2019-01-28 20:41:56 +03:00
Andrey Vlasovskikh
872921e6b7 Reformat code 2019-01-28 20:39:45 +03:00
Andrey Vlasovskikh
89788df95c Merge branch 'pull/173' 2019-01-28 20:38:03 +03:00
Andrey Vlasovskikh
6ccd8ed0b8 Added Matt Ellis to the list of contributors 2019-01-27 17:04:44 +03:00
Andrey Vlasovskikh
aa7e3bfa69 Updated changelog 2019-01-27 17:03:46 +03:00
Andrey Vlasovskikh
00154f2b9f Merge branch 'pull/170' 2019-01-27 16:48:05 +03:00
Matt Ellis
531a9c28ae VIM-1558 Support block inlays 2019-01-25 12:31:42 +00:00
Matt Ellis
56c4e3e31f VIM-1187 Fix performance with large files and relative line numbers 2019-01-08 13:47:05 +00:00
Matt Ellis
ef2497cadc Update to IJ 2018.3 2019-01-08 13:46:20 +00:00
Andrey Vlasovskikh
95f56a8869 Added Alex Plate to the contributors list 2018-12-31 18:10:44 +03:00
Andrey Vlasovskikh
f5b1112304 Updated changelog 2018-12-31 18:09:55 +03:00
Andrey Vlasovskikh
333a5be30b Merge branch 'pull/162' 2018-12-31 18:05:38 +03:00
Andrey Vlasovskikh
6c9e697892 VIM-620 Fixed handling nested <C-O> & <Esc> in Insert and Replace modes 2018-12-31 18:01:15 +03:00
Alex Plate
7663eb531e Write test to reproduce VIM-620 issue
Enter insert mode after single command produces incorrect behaviour.
Insert mode is not escaped after Esc
2018-12-29 00:36:51 +03:00
Alex Plate
5529bf284a VIM-798 Arrow keys for window navigation commands
Add new mappings to allow window navigation with arrow keys:
- ^W-<left> as ^W-h
- ^W-<down> as ^W-j
- ^W-<up> as ^W-k
- ^W-<right> as ^W-l
2018-10-01 20:19:25 +03:00
28 changed files with 748 additions and 220 deletions

View File

@@ -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.

View File

@@ -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
----------------

View File

@@ -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:

View File

@@ -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"
}

View File

@@ -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

View File

@@ -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"/>

View File

@@ -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));
}
}
}

View 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);
}
}
}
}

View File

@@ -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;

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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;
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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);
}
}
}

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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

View File

@@ -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;

View File

@@ -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;

View File

@@ -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;
}
private boolean scrollPage(@NotNull Editor editor, int pages, int height, int line, boolean partial) {
int visualTopLine = EditorHelper.getVisualLineAtTopOfScreen(editor);
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;
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);
}
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;
int offset = moveCaretToLineStartSkipLeading(editor, EditorHelper.visualLineToLogicalLine(editor, caretVisualLine));
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(), offset);
return success;
}
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));
public boolean scrollScreen(@NotNull final Editor editor, int rawCount, boolean down) {
final CaretModel caretModel = editor.getCaretModel();
final int currentLogicalLine = caretModel.getLogicalPosition().line;
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;
}
else {
moveCaret(editor, editor.getCaretModel().getPrimaryCaret(),
moveCaretToLineStartSkipLeading(editor, editor.getCaretModel().getPrimaryCaret()));
return false;
}
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);
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;
}
private static boolean scrollLineToTopOfScreen(@NotNull Editor editor, int line) {
int pos = line * editor.getLineHeight();
int verticalPos = editor.getScrollingModel().getVerticalScrollOffset();
editor.getScrollingModel().scrollVertically(pos);
return targetCaretVisualLine;
}
return verticalPos != editor.getScrollingModel().getVerticalScrollOffset();
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;
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;
}
// 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);
scrollLineToTopOfScreen(editor, 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;
}

View File

@@ -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;
}
}

View File

@@ -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);
}
}