mirror of
https://github.com/chylex/IntelliJ-IdeaVim.git
synced 2025-03-06 09:32:50 +01:00
VIM-1558 Support block inlays
This commit is contained in:
parent
56c4e3e31f
commit
531a9c28ae
resources/META-INF
src/com/maddyhome/idea/vim
action
internal
motion
screen
scroll
MotionScrollFirstScreenLineAction.javaMotionScrollFirstScreenLinePageStartAction.javaMotionScrollFirstScreenLineStartAction.javaMotionScrollHalfPageDownAction.javaMotionScrollHalfPageUpAction.javaMotionScrollLastScreenLineAction.javaMotionScrollLastScreenLinePageStartAction.javaMotionScrollLastScreenLineStartAction.javaMotionScrollMiddleScreenLineAction.javaMotionScrollMiddleScreenLineStartAction.java
group
helper
@ -352,6 +352,8 @@
|
||||
<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"/>
|
||||
|
||||
<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"/>
|
||||
|
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 myRandom = 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 = myRandom.nextInt(10); i < lineCount;) {
|
||||
|
||||
int offset = document.getLineStartOffset(i);
|
||||
|
||||
// Mostly above
|
||||
boolean above = myRandom.nextInt(10) > 3;
|
||||
|
||||
// Mostly do one, but occasionally throw in a bunch
|
||||
int count = myRandom.nextInt(10) > 7 ? myRandom.nextInt(5) : 1;
|
||||
for (int j = 0; j < count; j++) {
|
||||
|
||||
float factor = Math.max(1.75f * myRandom.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 + (myRandom.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 myFactor;
|
||||
private String myText;
|
||||
|
||||
MyBlockRenderer(float factor, String text) {
|
||||
myFactor = factor;
|
||||
myText = text;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int calcWidthInPixels(@NotNull Inlay inlay) {
|
||||
Editor editor = inlay.getEditor();
|
||||
FontMetrics fontMetrics = getFontMetrics(editor).metrics;
|
||||
return doCalcWidth(myText, 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(myText, g);
|
||||
|
||||
g.setColor(JBColor.GRAY);
|
||||
g.setFont(fontMetrics.getFont());
|
||||
g.drawString(myText, 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) * myFactor);
|
||||
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;
|
||||
private int lineHeight;
|
||||
|
||||
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
|
||||
lineHeight = (int) Math.ceil(font.createGlyphVector(context, "Ap").getVisualBounds().getHeight());
|
||||
}
|
||||
|
||||
public Font getFont() { return metrics.getFont(); }
|
||||
|
||||
public boolean isActual(Editor editor, String familyName, int size) {
|
||||
Font font = metrics.getFont();
|
||||
if (familyName != 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
|
||||
@ -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, ScreenLocation screenLocation, int line, boolean start) {
|
||||
|
||||
int visualLine = rawCount == 0
|
||||
final int scrollOffset = getNormalizedScrollOffset(editor);
|
||||
|
||||
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,45 @@ 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, 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 +911,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 +919,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 +953,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 +968,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 +1378,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);
|
||||
@ -1919,6 +1972,12 @@ public class MotionGroup {
|
||||
private int endOff;
|
||||
}
|
||||
|
||||
private enum ScreenLocation {
|
||||
TOP,
|
||||
MIDDLE,
|
||||
BOTTOM
|
||||
}
|
||||
|
||||
public int getLastFTCmd() {
|
||||
return lastFTCmd;
|
||||
}
|
||||
|
@ -48,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);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -146,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 -
|
||||
@ -225,6 +253,7 @@ public class EditorHelper {
|
||||
*/
|
||||
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;
|
||||
@ -593,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;
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user