Commit 07f988d3 authored by Dmitry Batrak's avatar Dmitry Batrak
Browse files

Inline hints with parameter names for literal method call arguments

As part of this change an API is introduced to add custom visual elements (inlays) into editor, not reflected in document text.
At the moment only passive inline elements with height equal to line height are supported.
parent 165ee331
Showing with 780 additions and 86 deletions
+780 -86
......@@ -18,5 +18,6 @@
<orderEntry type="module" module-name="resources-en" />
<orderEntry type="module" module-name="xml-psi-impl" />
<orderEntry type="library" exported="" name="ASM" level="project" />
<orderEntry type="module" module-name="platform-impl" />
</component>
</module>
\ No newline at end of file
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl;
import com.intellij.codeHighlighting.EditorBoundHighlightingPass;
import com.intellij.codeHighlighting.TextEditorHighlightingPass;
import com.intellij.codeHighlighting.TextEditorHighlightingPassFactory;
import com.intellij.codeHighlighting.TextEditorHighlightingPassRegistrar;
import com.intellij.codeInsight.folding.impl.ParameterNameFoldingManager;
import com.intellij.lang.folding.FoldingDescriptor;
import com.intellij.openapi.components.AbstractProjectComponent;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.progress.ProgressIndicator;
import com.intellij.openapi.progress.ProgressIndicatorProvider;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.registry.Registry;
import com.intellij.psi.*;
import com.intellij.util.containers.HashSet;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.*;
public class ParameterHintsPassFactory extends AbstractProjectComponent implements TextEditorHighlightingPassFactory {
private static final Key<Boolean> REPEATED_PASS = Key.create("RepeatedParameterHintsPass");
public ParameterHintsPassFactory(Project project, TextEditorHighlightingPassRegistrar registrar) {
super(project);
registrar.registerTextEditorHighlightingPass(this, null, null, false, -1);
}
@Nullable
@Override
public TextEditorHighlightingPass createHighlightingPass(@NotNull PsiFile file, @NotNull Editor editor) {
return new ParameterHintsPass(file, editor);
}
private static class ParameterHintsPass extends EditorBoundHighlightingPass {
private final Map<Integer, String> myAnnotations = new HashMap<>();
private ParameterHintsPass(@NotNull PsiFile file, @NotNull Editor editor) {
super(editor, file, true);
}
@Override
public void doCollectInformation(@NotNull ProgressIndicator progress) {
assert myDocument != null;
myAnnotations.clear();
if (!Registry.is("editor.inline.parameter.hints") || !(myFile instanceof PsiJavaFile)) return;
PsiJavaFile file = (PsiJavaFile) myFile;
PsiClass[] classes = file.getClasses();
for (PsiClass aClass : classes) {
ProgressIndicatorProvider.checkCanceled();
addElementsToFold(aClass);
}
}
private void addElementsToFold(PsiClass aClass) {
PsiElement[] children = aClass.getChildren();
for (PsiElement child : children) {
ProgressIndicatorProvider.checkCanceled();
if (child instanceof PsiMethod) {
PsiMethod method = (PsiMethod)child;
PsiCodeBlock body = method.getBody();
if (body != null) {
addCodeBlockFolds(body);
}
}
else if (child instanceof PsiField) {
PsiField field = (PsiField)child;
PsiExpression initializer = field.getInitializer();
if (initializer != null) {
addCodeBlockFolds(initializer);
} else if (field instanceof PsiEnumConstant) {
addCodeBlockFolds(field);
}
}
else if (child instanceof PsiClassInitializer) {
PsiClassInitializer initializer = (PsiClassInitializer)child;
addCodeBlockFolds(initializer);
}
else if (child instanceof PsiClass) {
addElementsToFold((PsiClass)child);
}
}
}
private void addCodeBlockFolds(PsiElement scope) {
scope.accept(new JavaRecursiveElementWalkingVisitor() {
@Override
public void visitClass(PsiClass aClass) {
addElementsToFold(aClass);
}
@Override
public void visitMethodCallExpression(PsiMethodCallExpression expression) {
inlineLiteralArgumentsNames(expression);
super.visitMethodCallExpression(expression);
}
@Override
public void visitNewExpression(PsiNewExpression expression) {
inlineLiteralArgumentsNames(expression);
super.visitNewExpression(expression);
}
});
}
private void inlineLiteralArgumentsNames(@NotNull PsiCallExpression expression) {
ParameterNameFoldingManager manager = new ParameterNameFoldingManager(expression);
List<FoldingDescriptor> descriptors = manager.getDescriptors();
for (FoldingDescriptor descriptor : descriptors) {
String text = descriptor.getPlaceholderText();
assert text != null;
int colonPos = text.indexOf(':');
myAnnotations.put(descriptor.getRange().getEndOffset(), text.substring(1, colonPos));
}
}
@Override
public void doApplyInformationToEditor() {
assert myDocument != null;
boolean firstTime = myEditor.getUserData(REPEATED_PASS) == null;
ParameterHintsPresentationManager presentationManager = ParameterHintsPresentationManager.getInstance();
Set<String> removedHints = new HashSet<>();
for (Inlay inlay : myEditor.getInlayModel().getInlineElementsInRange(0, myDocument.getTextLength())) {
if (!presentationManager.isParameterHint(inlay)) continue;
int offset = inlay.getOffset();
String oldText = presentationManager.getHintText(inlay);
String newText = myAnnotations.remove(offset);
if (!Objects.equals(newText, oldText)) {
if (newText == null) {
removedHints.add(oldText);
presentationManager.deleteHint(myEditor, inlay);
}
else {
presentationManager.replaceHint(myEditor, inlay, newText);
}
}
}
for (Map.Entry<Integer, String> e : myAnnotations.entrySet()) {
int offset = e.getKey();
String text = e.getValue();
presentationManager.addHint(myEditor, offset, text, !firstTime && !removedHints.contains(text));
}
myEditor.putUserData(REPEATED_PASS, Boolean.TRUE);
}
}
}
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.codeInsight.daemon.impl;
import com.intellij.ide.highlighter.JavaHighlightingColors;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.application.ModalityState;
import com.intellij.openapi.components.ServiceManager;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorCustomElementRenderer;
import com.intellij.openapi.editor.Inlay;
import com.intellij.openapi.editor.impl.FontInfo;
import com.intellij.openapi.editor.markup.TextAttributes;
import com.intellij.openapi.ui.GraphicsConfig;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.util.Key;
import com.intellij.ui.ColorUtil;
import com.intellij.util.Alarm;
import com.intellij.util.ui.GraphicsUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.awt.*;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
public class ParameterHintsPresentationManager implements Disposable {
private static final Key<FontMetrics> HINT_FONT_METRICS = Key.create("ParameterHintFontMetrics");
private static final Key<AnimationStep> ANIMATION_STEP = Key.create("ParameterHintAnimationStep");
private static final int ANIMATION_STEP_MS = 25;
private static final int ANIMATION_CHARS_PER_STEP = 3;
private final Alarm myAlarm = new Alarm(this);
public static ParameterHintsPresentationManager getInstance() {
return ServiceManager.getService(ParameterHintsPresentationManager.class);
}
private ParameterHintsPresentationManager() {
}
public boolean isParameterHint(@NotNull Inlay inlay) {
return inlay.getRenderer() instanceof MyRenderer;
}
public String getHintText(@NotNull Inlay inlay) {
EditorCustomElementRenderer renderer = inlay.getRenderer();
return renderer instanceof MyRenderer ? ((MyRenderer)renderer).myText : null;
}
public void addHint(@NotNull Editor editor, int offset, @NotNull String hintText, boolean useAnimation) {
MyRenderer renderer = new MyRenderer(editor, hintText, useAnimation);
Inlay inlay = editor.getInlayModel().addInlineElement(offset, renderer);
if (useAnimation && inlay != null) {
scheduleRendererUpdate(editor, inlay);
}
}
public void deleteHint(@NotNull Editor editor, @NotNull Inlay hint) {
updateRenderer(editor, hint, null);
}
public void replaceHint(@NotNull Editor editor, @NotNull Inlay hint, @NotNull String newText) {
updateRenderer(editor, hint, newText);
}
private void updateRenderer(@NotNull Editor editor, @NotNull Inlay hint, @Nullable String newText) {
MyRenderer renderer = (MyRenderer)hint.getRenderer();
renderer.update(editor, newText);
hint.updateSize();
scheduleRendererUpdate(editor, hint);
}
@Override
public void dispose() {}
private void scheduleRendererUpdate(Editor editor, Inlay inlay) {
AnimationStep step = editor.getUserData(ANIMATION_STEP);
if (step == null) {
editor.putUserData(ANIMATION_STEP, step = new AnimationStep(editor));
}
step.inlays.add(inlay);
scheduleAnimationStep(step);
}
private void scheduleAnimationStep(AnimationStep step) {
myAlarm.cancelRequest(step);
myAlarm.addRequest(step, ANIMATION_STEP_MS, ModalityState.any());
}
private static Font getFont(@NotNull Editor editor) {
return getFontMetrics(editor).getFont();
}
private static FontMetrics getFontMetrics(@NotNull Editor editor) {
String familyName = UIManager.getFont("Label.font").getFamily();
int size = Math.max(1, editor.getColorsScheme().getEditorFontSize() - 1);
FontMetrics metrics = editor.getUserData(HINT_FONT_METRICS);
if (metrics != null) {
Font font = metrics.getFont();
if (!familyName.equals(font.getFamily()) || size != font.getSize()) metrics = null;
}
if (metrics == null) {
Font font = new Font(familyName, Font.PLAIN, size);
metrics = FontInfo.createReferenceGraphics().getFontMetrics(font);
editor.putUserData(HINT_FONT_METRICS, metrics);
}
return metrics;
}
private static class MyRenderer implements EditorCustomElementRenderer {
private String myText;
private int startWidth;
private int steps;
private int step;
private MyRenderer(Editor editor, String text, boolean animated) {
updateState(editor, text);
if (!animated) step = steps + 1;
}
public void update(Editor editor, String newText) {
updateState(editor, newText);
}
private void updateState(Editor editor, String text) {
FontMetrics metrics = getFontMetrics(editor);
startWidth = doCalcWidth(myText, metrics);
myText = text;
int endWidth = doCalcWidth(myText, metrics);
step = 1;
steps = Math.max(1, Math.abs(endWidth - startWidth) / metrics.charWidth('a') / ANIMATION_CHARS_PER_STEP);
}
public boolean nextStep() {
return ++step <= steps;
}
@Override
public int calcWidthInPixels(@NotNull Editor editor) {
FontMetrics metrics = getFontMetrics(editor);
int endWidth = doCalcWidth(myText, metrics);
return step <= steps ? Math.max(1, startWidth + (endWidth - startWidth) / steps * step) : endWidth;
}
private static int doCalcWidth(@Nullable String text, @NotNull FontMetrics fontMetrics) {
return text == null ? 0 : fontMetrics.stringWidth(text) + 14;
}
@Override
public void paint(@NotNull Editor editor, @NotNull Graphics g, @NotNull Rectangle r) {
if (myText != null && (step > steps || startWidth != 0)) {
TextAttributes attributes = editor.getColorsScheme().getAttributes(JavaHighlightingColors.INLINE_PARAMETER_HINT);
if (attributes != null) {
GraphicsConfig config = GraphicsUtil.setupAAPainting(g);
int shadeRectHeight = Math.min(4, r.height - 3);
Color backgroundColor = attributes.getBackgroundColor();
g.setColor(ColorUtil.brighter(backgroundColor, 1));
g.fillRoundRect(r.x + 2, r.y + 1, r.width - 4, shadeRectHeight, 4, 4);
g.setColor(ColorUtil.darker(backgroundColor, 1));
g.fillRoundRect(r.x + 2, r.y + r.height - shadeRectHeight - 1, r.width - 4, shadeRectHeight, 4, 4);
g.setColor(backgroundColor);
g.fillRoundRect(r.x + 2, r.y + 2, r.width - 4, r.height - 4, 4, 4);
g.setColor(attributes.getForegroundColor());
g.setFont(getFont(editor));
FontMetrics metrics = g.getFontMetrics();
Shape savedClip = g.getClip();
g.clipRect(r.x + 3, r.y + 2, r.width - 6, r.height - 4);
g.drawString(myText, r.x + 7, r.y + (r.height + metrics.getAscent() - metrics.getDescent()) / 2);
g.setClip(savedClip);
config.restore();
}
}
}
}
private class AnimationStep implements Runnable {
private final Editor myEditor;
private final Set<Inlay> inlays = new HashSet<>();
AnimationStep(Editor editor) {
myEditor = editor;
}
@Override
public void run() {
Iterator<Inlay> it = inlays.iterator();
while (it.hasNext()) {
Inlay inlay = it.next();
if (inlay.isValid()) {
MyRenderer renderer = (MyRenderer)inlay.getRenderer();
if (!renderer.nextStep()) {
it.remove();
}
if (renderer.calcWidthInPixels(myEditor) == 0) {
Disposer.dispose(inlay);
}
else {
inlay.updateSize();
}
}
else {
it.remove();
}
}
if (inlays.isEmpty()) {
myEditor.putUserData(ANIMATION_STEP, null);
}
else {
scheduleAnimationStep(this);
}
}
}
}
......@@ -85,7 +85,9 @@ public class JavaHighlightingColors {
= TextAttributesKey.createTextAttributesKey("ANONYMOUS_CLASS_NAME_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES
= TextAttributesKey.createTextAttributesKey("IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES", CLASS_NAME_ATTRIBUTES);
public static final TextAttributesKey TYPE_PARAMETER_NAME_ATTRIBUTES
public static final TextAttributesKey INLINE_PARAMETER_HINT
= TextAttributesKey.createTextAttributesKey("INLINE_PARAMETER_HINT");
public static final TextAttributesKey TYPE_PARAMETER_NAME_ATTRIBUTES
= TextAttributesKey.createTextAttributesKey("TYPE_PARAMETER_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.PARAMETER);
public static final TextAttributesKey INTERFACE_NAME_ATTRIBUTES
= TextAttributesKey.createTextAttributesKey("INTERFACE_NAME_ATTRIBUTES", DefaultLanguageHighlighterColors.INTERFACE_NAME);
......
......@@ -70,6 +70,7 @@ public class JavaColorSettingsPage implements ColorSettingsPage, InspectionColor
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.reassigned.local.variable"), JavaHighlightingColors.REASSIGNED_LOCAL_VARIABLE_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.reassigned.parameter"), JavaHighlightingColors.REASSIGNED_PARAMETER_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.implicit.anonymous.parameter"), JavaHighlightingColors.IMPLICIT_ANONYMOUS_CLASS_PARAMETER_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.inline.parameter.hint"), JavaHighlightingColors.INLINE_PARAMETER_HINT),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.instance.field"), JavaHighlightingColors.INSTANCE_FIELD_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.instance.final.field"), JavaHighlightingColors.INSTANCE_FINAL_FIELD_ATTRIBUTES),
new AttributesDescriptor(OptionsBundle.message("options.java.attribute.descriptor.static.field"), JavaHighlightingColors.STATIC_FIELD_ATTRIBUTES),
......
......@@ -32,7 +32,7 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
class ParameterNameFoldingManager {
public class ParameterNameFoldingManager {
private static final List<Couple<String>> COMMONLY_USED_PARAMETER_PAIR = ContainerUtil.newArrayList(
Couple.of("begin", "end"),
Couple.of("start", "end"),
......@@ -46,7 +46,7 @@ class ParameterNameFoldingManager {
@NotNull
private final List<FoldingDescriptor> myDescriptors;
ParameterNameFoldingManager(@NotNull PsiCallExpression callExpression) {
public ParameterNameFoldingManager(@NotNull PsiCallExpression callExpression) {
PsiExpression[] callArguments = getArguments(callExpression);
JavaResolveResult resolveResult = callExpression.resolveMethodGenerics();
......@@ -84,7 +84,7 @@ class ParameterNameFoldingManager {
}
@NotNull
List<FoldingDescriptor> getDescriptors() {
public List<FoldingDescriptor> getDescriptors() {
return myDescriptors;
}
......
......@@ -39,7 +39,7 @@ public class EditorDocumentPriorities {
public static final int SOFT_WRAP_MODEL = 100;
public static final int EDITOR_TEXT_WIDTH_CACHE = 110;
public static final int CARET_MODEL = 120;
public static final int SELECTION_MODEL = 140;
public static final int INLAY_MODEL = 150;
public static final int EDITOR_DOCUMENT_ADAPTER = 160;
private EditorDocumentPriorities() {
......
......@@ -377,4 +377,7 @@ public interface Editor extends UserDataHolder {
@NotNull
IndentsModel getIndentsModel();
@NotNull
InlayModel getInlayModel();
}
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor;
import org.jetbrains.annotations.NotNull;
import java.awt.*;
/**
* An interface, defining size and representation of custom visual element in editor.
*
* @see InlayModel#addInlineElement(int, EditorCustomElementRenderer)
* @see Inlay#getRenderer()
*/
public interface EditorCustomElementRenderer {
/**
* Defines width of custom element (in pixels)
*/
int calcWidthInPixels(@NotNull Editor editor);
/**
* Implements painting for the custom region. Rectangle passed as a parameter defines target region where painting should be performed.
*/
void paint(@NotNull Editor editor, @NotNull Graphics g, @NotNull Rectangle r);
}
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor;
import com.intellij.openapi.Disposable;
import com.intellij.openapi.util.UserDataHolderEx;
import org.jetbrains.annotations.NotNull;
/**
* A custom visual element displayed in editor. It is associated with a certain position in a document, but is not
* represented in document text in any way. Inlay's document position (offset) is updated on document changes just like
* for a {@link RangeMarker}. Inlay becomes invalid on explicit disposal, or when a document range fully containing inlay's offset,
* is deleted.
* <p>
* WARNING! This is an experimental API, it can change at any time.
*/
public interface Inlay extends Disposable, UserDataHolderEx {
/**
* Tells whether this element is valid. Inlay becomes invalid on explicit disposal,
* or when a document range fully containing inlay's offset, is deleted.
*/
boolean isValid();
/**
* Returns current inlay's position in the document. This position is updated on document changes just like for a {@link RangeMarker}.
*/
int getOffset();
/**
* Returns renderer, which defines size and representation for this inlay.
*/
@NotNull
EditorCustomElementRenderer getRenderer();
/**
* Returns current inlay's width. Width is defined at inlay's creation using information returned by inlay's renderer.
* To change width, {@link #updateSize()} method should be called.
*/
int getWidthInPixels();
/**
* Updates inlay's size by querying information from inlay's renderer.
*
* @see EditorCustomElementRenderer#calcWidthInPixels(Editor)
*/
void updateSize();
}
/*
* Copyright 2000-2016 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.intellij.openapi.editor;
import com.intellij.openapi.Disposable;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.util.EventListener;
import java.util.List;
/**
* Provides an ability to introduce custom visual elements into editor's representation.
* Such elements are not reflected in document contents.
* <p>
* WARNING! This is an experimental API, it can change at any time.
*/
public interface InlayModel {
/**
* Introduces an inline visual element at a given offset, its width and appearance is defined by the provided renderer. With respect to
* document changes, created element behaves in a similar way to a zero-range {@link RangeMarker}. This method returns <code>null</code>
* if requested element cannot be created, e.g. if corresponding functionality is not supported by current editor instance.
*/
@Nullable
Inlay addInlineElement(int offset, @NotNull EditorCustomElementRenderer renderer);
/**
* Returns a list of inline elements for a given offset range (both limits are inclusive). Returned list is sorted by offset.
*/
@NotNull
List<Inlay> getInlineElementsInRange(int startOffset, int endOffset);
/**
* Tells whether there exists an inline visual element at a given offset.
*/
boolean hasInlineElementAt(int offset);
/**
* Tells whether there exists an inline visual element at a given visual position.
* Only visual position to the left of the element is recognized.
*/
boolean hasInlineElementAt(@NotNull VisualPosition visualPosition);
/**
* Adds a listener that will be notified after adding, updating and removal of custom visual elements.
*/
void addListener(@NotNull Listener listener, @NotNull Disposable disposable);
interface Listener extends EventListener {
void onAdded(@NotNull Inlay inlay);
void onUpdated(@NotNull Inlay inlay);
void onRemoved(@NotNull Inlay inlay);
}
/**
* An adapter useful for the cases, when the same action is to be performed after custom visual element's adding, updating and removal.
*/
abstract class SimpleAdapter implements Listener {
@Override
public void onAdded(@NotNull Inlay inlay) {
onUpdated(inlay);
}
@Override
public void onUpdated(@NotNull Inlay inlay) {}
@Override
public void onRemoved(@NotNull Inlay inlay) {
onUpdated(inlay);
}
}
}
......@@ -124,6 +124,12 @@ class LazyEditor extends UserDataHolderBase implements Editor {
return getEditor().getSoftWrapModel();
}
@NotNull
@Override
public InlayModel getInlayModel() {
return getEditor().getInlayModel();
}
@Override
@NotNull
public EditorSettings getSettings() {
......
......@@ -299,6 +299,12 @@ public class EditorWindowImpl extends UserDataHolderBase implements EditorWindow
return myDelegate.getSettings();
}
@NotNull
@Override
public InlayModel getInlayModel() {
throw new UnsupportedOperationException();
}
@Override
public void reinitSettings() {
myDelegate.reinitSettings();
......
......@@ -36,7 +36,6 @@ import com.intellij.ui.ListCellRendererWrapper;
import com.intellij.ui.components.JBCheckBox;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import sun.swing.SwingUtilities2;
import javax.swing.*;
......@@ -155,6 +154,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
UISettings settings = UISettings.getInstance();
int _fontSize = getIntValue(myComponent.myFontSizeCombo, settings.FONT_SIZE);
int _presentationFontSize = getIntValue(myComponent.myPresentationModeFontSize, settings.PRESENTATION_MODE_FONT_SIZE);
boolean update = false;
boolean shouldUpdateUI = false;
String _fontFace = myComponent.myFontCombo.getFontName();
LafManager lafManager = LafManager.getInstance();
......@@ -162,6 +162,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
settings.FONT_SIZE = _fontSize;
settings.FONT_FACE = _fontFace;
shouldUpdateUI = true;
update = true;
}
if (_presentationFontSize != settings.PRESENTATION_MODE_FONT_SIZE) {
......@@ -185,7 +186,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
}
settings.ANIMATE_WINDOWS = myComponent.myAnimateWindowsCheckBox.isSelected();
boolean update = settings.SHOW_TOOL_WINDOW_NUMBERS != myComponent.myWindowShortcutsCheckBox.isSelected();
update |= settings.SHOW_TOOL_WINDOW_NUMBERS != myComponent.myWindowShortcutsCheckBox.isSelected();
settings.SHOW_TOOL_WINDOW_NUMBERS = myComponent.myWindowShortcutsCheckBox.isSelected();
update |= settings.HIDE_TOOL_STRIPES != !myComponent.myShowToolStripesCheckBox.isSelected();
settings.HIDE_TOOL_STRIPES = !myComponent.myShowToolStripesCheckBox.isSelected();
......@@ -199,6 +200,7 @@ public class AppearanceConfigurable extends BaseConfigurable implements Searchab
settings.CYCLE_SCROLLING = myComponent.myCycleScrollingCheckBox.isSelected();
if (settings.OVERRIDE_NONIDEA_LAF_FONTS != myComponent.myOverrideLAFFonts.isSelected()) {
shouldUpdateUI = true;
update = true;
}
settings.OVERRIDE_NONIDEA_LAF_FONTS = myComponent.myOverrideLAFFonts.isSelected();
settings.MOVE_MOUSE_ON_DEFAULT_BUTTON = myComponent.myMoveMouseOnDefaultButtonCheckBox.isSelected();
......
......@@ -29,6 +29,7 @@ import com.intellij.openapi.actionSystem.DataContext;
import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.util.ui.MacUIUtil;
import org.jetbrains.annotations.NotNull;
......@@ -61,6 +62,13 @@ public class BackspaceAction extends TextComponentEditorAction {
return;
}
VisualPosition caretPosition = editor.getCaretModel().getVisualPosition();
if (caretPosition.column > 0 &&
editor.getInlayModel().hasInlineElementAt(new VisualPosition(caretPosition.line, caretPosition.column - 1))) {
editor.getCaretModel().moveCaretRelatively(-1, 0, false, false, EditorUtil.isCurrentCaretPrimary(editor));
return;
}
int lineNumber = editor.getCaretModel().getLogicalPosition().line;
int colNumber = editor.getCaretModel().getLogicalPosition().column;
Document document = editor.getDocument();
......@@ -81,7 +89,6 @@ public class BackspaceAction extends TextComponentEditorAction {
}
else {
document.deleteString(offset - 1, offset);
editor.getCaretModel().moveToOffset(offset - 1, true);
}
}
}
......
......@@ -29,6 +29,7 @@ import com.intellij.openapi.command.CommandProcessor;
import com.intellij.openapi.editor.*;
import com.intellij.openapi.editor.actionSystem.EditorAction;
import com.intellij.openapi.editor.actionSystem.EditorWriteActionHandler;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.ide.CopyPasteManager;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.util.ui.MacUIUtil;
......@@ -50,7 +51,12 @@ public class DeleteAction extends EditorAction {
CopyPasteManager.getInstance().stopKillRings();
SelectionModel selectionModel = editor.getSelectionModel();
if (!selectionModel.hasSelection()) {
deleteCharAtCaret(editor);
if (editor.getInlayModel().hasInlineElementAt(editor.getCaretModel().getVisualPosition())) {
editor.getCaretModel().moveCaretRelatively(1, 0, false, false, EditorUtil.isCurrentCaretPrimary(editor));
}
else {
deleteCharAtCaret(editor);
}
}
else {
EditorModificationUtil.deleteSelectedText(editor);
......@@ -98,7 +104,6 @@ public class DeleteAction extends EditorAction {
}
else {
document.deleteString(offset, offset + 1);
editor.getCaretModel().moveToOffset(offset);
}
return;
}
......
......@@ -892,6 +892,10 @@ public final class EditorUtil {
return attributes == TextAttributes.ERASE_MARKER ||
(attributes != null && (attributes.getFontType() != Font.PLAIN || attributes.getForegroundColor() != null));
}
public static boolean isCurrentCaretPrimary(@NotNull Editor editor) {
return editor.getCaretModel().getCurrentCaret() == editor.getCaretModel().getPrimaryCaret();
}
}
......@@ -109,6 +109,8 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
private int myStartVirtualOffset;
private int myEndVirtualOffset;
private boolean myAfterInlayOnDeletion;
CaretImpl(EditorImpl editor) {
myEditor = editor;
......@@ -125,7 +127,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
Document doc = myEditor.getDocument();
if (myOffset > doc.getTextLength() || savedBeforeBulkCaretMarker != null) return;
savedBeforeBulkCaretMarker = doc.createRangeMarker(myOffset, myOffset);
beforeDocumentChange();
saveSelectionBeforeDocumentChange();
}
void onBulkDocumentUpdateFinished() {
......@@ -143,10 +145,17 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
}
releaseBulkCaretMarker();
}
documentChanged();
updateSelectionOnDocumentChange();
}
void beforeDocumentChange(DocumentEvent e) {
int startOffset = e.getOffset();
myAfterInlayOnDeletion = e.getNewLength() == 0 && myOffset >= startOffset && myOffset <= startOffset + e.getOldLength() &&
myEditor.getInlayModel().hasInlineElementAt(startOffset);
saveSelectionBeforeDocumentChange();
}
public void beforeDocumentChange() {
void saveSelectionBeforeDocumentChange() {
RangeMarker marker = mySelectionMarker;
if (marker != null && marker.isValid()) {
startBefore = marker.getStartOffset();
......@@ -154,7 +163,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
}
}
public void documentChanged() {
private void updateSelectionOnDocumentChange() {
RangeMarker marker = mySelectionMarker;
if (marker != null) {
int endAfter;
......@@ -234,7 +243,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
}
@Override
public void moveCaretRelatively(final int columnShift, final int lineShift, final boolean withSelection, final boolean scrollToCaret) {
public void moveCaretRelatively(final int _columnShift, final int lineShift, final boolean withSelection, final boolean scrollToCaret) {
assertIsDispatchThread();
if (mySkipChangeRequests) {
return;
......@@ -246,6 +255,21 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
CopyPasteManager.getInstance().stopKillRings();
}
myEditor.getCaretModel().doWithCaretMerging(() -> {
int columnShift = _columnShift;
if (withSelection && lineShift == 0) {
if (columnShift == -1) {
if (myEditor.getInlayModel().hasInlineElementAt(
new VisualPosition(myVisibleCaret.line, myVisibleCaret.column - (hasSelection() && myOffset == getSelectionEnd() ? 2 : 1)))) {
columnShift = -2;
}
}
else if (columnShift == 1) {
if (myEditor.getInlayModel().hasInlineElementAt(
new VisualPosition(myVisibleCaret.line, myVisibleCaret.column + (hasSelection() && myOffset == getSelectionStart() ? 1 : 0)))) {
columnShift = 2;
}
}
}
int oldOffset = myOffset;
final int leadSelectionOffset = getLeadSelectionOffset();
final VisualPosition leadSelectionPosition = getLeadSelectionPosition();
......@@ -780,7 +804,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
*/
void updateVisualPosition() {
VerticalInfo oldInfo = myCaretInfo;
LogicalPosition visUnawarePos = new LogicalPosition(myLogicalCaret.line, myLogicalCaret.column);
LogicalPosition visUnawarePos = new LogicalPosition(myLogicalCaret.line, myLogicalCaret.column, myLogicalCaret.leansForward);
setCurrentLogicalCaret(visUnawarePos);
myVisibleCaret = myEditor.logicalToVisualPosition(myLogicalCaret);
updateVisualLineInfo();
......@@ -794,7 +818,7 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
myVisualLineEnd = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(myVisibleCaret.line + 1, 0)));
}
void updateCaretPosition(@NotNull final DocumentEventImpl event) {
void afterDocumentChange(@NotNull final DocumentEventImpl event) {
final DocumentEx document = myEditor.getDocument();
if (document.isInBulkUpdate()) return;
boolean performSoftWrapAdjustment = event.getNewLength() > 0 // We want to put caret just after the last added symbol
......@@ -822,28 +846,45 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
int startOffset = event.getOffset();
int oldEndOffset = startOffset + event.getOldLength();
int newOffset = myOffset;
if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(event)) {
newOffset += event.getNewLength() - event.getOldLength();
}
else if (myOffset >= startOffset && myOffset <= oldEndOffset) {
newOffset = Math.min(newOffset, startOffset + event.getNewLength());
if (myAfterInlayOnDeletion) {
VisualPosition pos = myEditor.offsetToVisualPosition(startOffset, true, false);
moveToVisualPosition(pos);
}
else {
int newOffset = myOffset;
newOffset = Math.min(newOffset, document.getTextLength());
if (myOffset > oldEndOffset || myOffset == oldEndOffset && needToShiftWhiteSpaces(event)) {
newOffset += event.getNewLength() - event.getOldLength();
}
else if (myOffset >= startOffset && myOffset <= oldEndOffset) {
newOffset = Math.min(newOffset, startOffset + event.getNewLength());
}
if (myOffset != startOffset) {
LogicalPosition pos = myEditor.offsetToLogicalPosition(newOffset);
moveToLogicalPosition(new LogicalPosition(pos.line, pos.column + myVirtualSpaceOffset), // retain caret in the virtual space
performSoftWrapAdjustment, null, true);
}
else {
moveToOffset(newOffset, performSoftWrapAdjustment);
newOffset = Math.min(newOffset, document.getTextLength());
if (myOffset != startOffset) {
LogicalPosition pos = myEditor.offsetToLogicalPosition(newOffset);
moveToLogicalPosition(new LogicalPosition(pos.line, pos.column + myVirtualSpaceOffset), // retain caret in the virtual space
performSoftWrapAdjustment, null, true);
}
else {
moveToOffset(newOffset, performSoftWrapAdjustment);
}
}
}
updateVisualLineInfo();
updateSelectionOnDocumentChange();
}
void onInlayAdded(int offset) {
if (offset == myOffset && myLogicalCaret.leansForward) {
VisualPosition pos = myEditor.offsetToVisualPosition(myOffset, true, false);
moveToVisualPosition(pos);
}
else {
updateVisualPosition();
}
}
private boolean needToShiftWhiteSpaces(final DocumentEvent e) {
......@@ -1515,10 +1556,13 @@ public class CaretImpl extends UserDataHolderBase implements Caret, Dumpable {
private void invalidateRangeMarkerVisualPositions(RangeMarker marker) {
SoftWrapModelImpl model = myEditor.getSoftWrapModel();
if (!myEditor.offsetToVisualPosition(marker.getStartOffset(), true, false).equals(myRangeMarkerStartPosition) &&
model.getSoftWrap(marker.getStartOffset()) == null ||
!myEditor.offsetToVisualPosition(marker.getEndOffset(), false, true).equals(myRangeMarkerEndPosition)
&& model.getSoftWrap(marker.getEndOffset()) == null) {
InlayModelImpl inlayModel = myEditor.getInlayModel();
int startOffset = marker.getStartOffset();
int endOffset = marker.getEndOffset();
if (!myEditor.offsetToVisualPosition(startOffset, true, false).equals(myRangeMarkerStartPosition) &&
model.getSoftWrap(startOffset) == null && !inlayModel.hasInlineElementAt(startOffset) ||
!myEditor.offsetToVisualPosition(endOffset, false, true).equals(myRangeMarkerEndPosition)
&& model.getSoftWrap(endOffset) == null && !inlayModel.hasInlineElementAt(endOffset)) {
myRangeMarkerStartPosition = null;
myRangeMarkerEndPosition = null;
}
......
......@@ -43,7 +43,7 @@ import org.jetbrains.annotations.Nullable;
import java.util.*;
public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Disposable, Dumpable {
public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener, Disposable, Dumpable, InlayModel.Listener {
private final EditorImpl myEditor;
private final EventDispatcher<CaretListener> myCaretListeners = EventDispatcher.create(CaretListener.class);
......@@ -83,7 +83,7 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
myIsInUpdate = false;
doWithCaretMerging(() -> {
for (CaretImpl caret : myCarets) {
caret.updateCaretPosition((DocumentEventImpl)e);
caret.afterDocumentChange((DocumentEventImpl)e);
}
});
}
......@@ -94,6 +94,11 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
@Override
public void beforeDocumentChange(DocumentEvent e) {
if (!myEditor.getDocument().isInBulkUpdate()) {
for (CaretImpl caret : myCarets) {
caret.beforeDocumentChange(e);
}
}
myIsInUpdate = true;
}
......@@ -535,6 +540,27 @@ public class CaretModelImpl implements CaretModel, PrioritizedDocumentListener,
", all carets: " + ContainerUtil.map(myCarets, CaretImpl::dumpState) + "]";
}
@Override
public void onAdded(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
int offset = inlay.getOffset();
for (CaretImpl caret : myCarets) {
caret.onInlayAdded(offset);
}
}
@Override
public void onRemoved(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
doWithCaretMerging(this::updateVisualPosition);
}
@Override
public void onUpdated(@NotNull Inlay inlay) {
if (myEditor.getDocument().isInBulkUpdate()) return;
updateVisualPosition();
}
private static class VisualPositionComparator implements Comparator<VisualPosition> {
private static final VisualPositionComparator INSTANCE = new VisualPositionComparator();
......
......@@ -45,6 +45,7 @@ import com.intellij.openapi.editor.event.EditorMouseEventArea;
import com.intellij.openapi.editor.ex.*;
import com.intellij.openapi.editor.ex.util.EditorUIUtil;
import com.intellij.openapi.editor.ex.util.EditorUtil;
import com.intellij.openapi.editor.impl.view.VisualLinesIterator;
import com.intellij.openapi.editor.markup.*;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.DumbService;
......@@ -296,24 +297,27 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
return;
}
int startVisualLine = myEditor.yToVisibleLine(clip.y);
int endVisualLine = myEditor.yToVisibleLine(clip.y + clip.height);
// paint all backgrounds
int gutterSeparatorX = getWhitespaceSeparatorOffset();
paintBackground(g, clip, 0, gutterSeparatorX, backgroundColor);
paintBackground(g, clip, gutterSeparatorX, getFoldingAreaWidth(), myEditor.getBackgroundColor());
int firstVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y - myEditor.getLineHeight())));
int lastVisibleOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, clip.y + clip.height + myEditor.getLineHeight())));
int firstVisibleOffset = myEditor.visualLineStartOffset(startVisualLine);
int lastVisibleOffset = myEditor.visualLineStartOffset(endVisualLine + 1);
paintEditorBackgrounds(g, firstVisibleOffset, lastVisibleOffset);
Object hint = g.getRenderingHint(RenderingHints.KEY_ANTIALIASING);
if (!UIUtil.isRetina()) g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_OFF);
try {
paintAnnotations(g, clip);
paintAnnotations(g, startVisualLine, endVisualLine);
paintLineMarkers(g, firstVisibleOffset, lastVisibleOffset);
paintFoldingLines(g, clip);
paintFoldingTree(g, clip, firstVisibleOffset, lastVisibleOffset);
paintLineNumbers(g, clip);
paintLineNumbers(g, startVisualLine, endVisualLine);
}
finally {
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, hint);
......@@ -409,7 +413,7 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
}
private void paintAnnotations(Graphics2D g, Rectangle clip) {
private void paintAnnotations(Graphics2D g, int startVisualLine, int endVisualLine) {
int x = getAnnotationsAreaOffset();
int w = getAnnotationsAreaWidthEx();
......@@ -425,32 +429,31 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
TextAnnotationGutterProvider gutterProvider = myTextAnnotationGutters.get(i);
int lineHeight = myEditor.getLineHeight();
int startLineNumber = myEditor.yToVisibleLine(clip.y);
int endLineNumber = myEditor.yToVisibleLine(clip.y + clip.height) + 1;
int lastLine = myEditor.logicalToVisualPosition(
new LogicalPosition(endLineNumber(), 0))
.line;
endLineNumber = Math.min(endLineNumber, lastLine + 1);
if (startLineNumber >= endLineNumber) {
int lastLine = myEditor.logicalToVisualPosition(new LogicalPosition(endLineNumber(), 0)).line;
endVisualLine = Math.min(endVisualLine, lastLine);
if (startVisualLine > endVisualLine) {
break;
}
int annotationSize = myTextAnnotationGutterSizes.get(i);
for (int j = startLineNumber; j < endLineNumber; j++) {
int logLine = myEditor.visualToLogicalPosition(new VisualPosition(j, 0)).line;
VisualLinesIterator visLinesIterator = new VisualLinesIterator(myEditor, startVisualLine);
while (!visLinesIterator.atEnd() && visLinesIterator.getVisualLine() <= endVisualLine) {
int logLine = visLinesIterator.getStartLogicalLine();
int y = visLinesIterator.getY();
String s = gutterProvider.getLineText(logLine, myEditor);
final EditorFontType style = gutterProvider.getStyle(logLine, myEditor);
final Color bg = gutterProvider.getBgColor(logLine, myEditor);
if (bg != null) {
g.setColor(bg);
g.fillRect(x, myEditor.visibleLineToY(j), annotationSize, lineHeight);
g.fillRect(x, y, annotationSize, lineHeight);
}
g.setColor(myEditor.getColorsScheme().getColor(gutterProvider.getColor(logLine, myEditor)));
g.setFont(myEditor.getColorsScheme().getFont(style));
if (!StringUtil.isEmpty(s)) {
// we leave half of the gap before the text
g.drawString(s, GAP_BETWEEN_ANNOTATIONS / 2 + x, myEditor.visibleLineToY(j) + myEditor.getAscent());
g.drawString(s, GAP_BETWEEN_ANNOTATIONS / 2 + x, y + myEditor.getAscent());
}
visLinesIterator.advance();
}
x += annotationSize;
......@@ -496,12 +499,12 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
}
}
private void paintLineNumbers(Graphics2D g, Rectangle clip) {
private void paintLineNumbers(Graphics2D g, int startVisualLine, int endVisualLine) {
if (isLineNumbersShown()) {
int offset = getLineNumberAreaOffset() + myLineNumberAreaWidth;
doPaintLineNumbers(g, clip, offset, myLineNumberConvertor);
doPaintLineNumbers(g, startVisualLine, endVisualLine, offset, myLineNumberConvertor);
if (myAdditionalLineNumberConvertor != null) {
doPaintLineNumbers(g, clip, offset + getAreaWidthWithGap(myAdditionalLineNumberAreaWidth), myAdditionalLineNumberConvertor);
doPaintLineNumbers(g, startVisualLine, endVisualLine, offset + getAreaWidthWithGap(myAdditionalLineNumberAreaWidth), myAdditionalLineNumberConvertor);
}
}
}
......@@ -525,14 +528,12 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
return getFontMetrics(getFontForLineNumbers()).stringWidth(Integer.toString(maxLineNumber + 1));
}
private void doPaintLineNumbers(Graphics2D g, Rectangle clip, int offset, @NotNull TIntFunction convertor) {
int startLineNumber = myEditor.yToVisibleLine(clip.y);
int endLineNumber = myEditor.yToVisibleLine(clip.y + clip.height) + 1;
private void doPaintLineNumbers(Graphics2D g, int startVisualLine, int endVisualLine, int offset, @NotNull TIntFunction convertor) {
int lastLine = myEditor.logicalToVisualPosition(
new LogicalPosition(endLineNumber(), 0))
.line;
endLineNumber = Math.min(endLineNumber, lastLine + 1);
if (startLineNumber >= endLineNumber) {
endVisualLine = Math.min(endVisualLine, lastLine);
if (startVisualLine > endVisualLine) {
return;
}
......@@ -543,28 +544,29 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
AffineTransform old = setMirrorTransformIfNeeded(g, getLineNumberAreaOffset(), getLineNumberAreaWidth());
try {
for (int i = startLineNumber; i < endLineNumber; i++) {
LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(i, 0));
if (EditorUtil.getSoftWrapCountAfterLineStart(myEditor, logicalPosition) > 0) {
continue;
}
int logLine = convertor.execute(logicalPosition.line);
if (logLine >= 0) {
String s = String.valueOf(logLine + 1);
int startY = myEditor.visibleLineToY(i);
if (myEditor.isInDistractionFreeMode()) {
Color fgColor = myTextFgColors.get(i);
g.setColor(fgColor != null ? fgColor : color != null ? color : JBColor.blue);
}
VisualLinesIterator visLinesIterator = new VisualLinesIterator(myEditor, startVisualLine);
while (!visLinesIterator.atEnd() && visLinesIterator.getVisualLine() <= endVisualLine) {
LogicalPosition logicalPosition = myEditor.visualToLogicalPosition(new VisualPosition(visLinesIterator.getVisualLine(), 0));
if (EditorUtil.getSoftWrapCountAfterLineStart(myEditor, logicalPosition) <= 0) {
int logLine = convertor.execute(visLinesIterator.getStartLogicalLine());
if (logLine >= 0) {
String s = String.valueOf(logLine + 1);
int startY = visLinesIterator.getY();
if (myEditor.isInDistractionFreeMode()) {
Color fgColor = myTextFgColors.get(visLinesIterator.getVisualLine());
g.setColor(fgColor != null ? fgColor : color != null ? color : JBColor.blue);
}
int textOffset = isMirrored() ?
offset - getLineNumberAreaWidth() - 1:
offset - g.getFontMetrics().stringWidth(s);
int textOffset = isMirrored() ?
offset - getLineNumberAreaWidth() - 1:
offset - g.getFontMetrics().stringWidth(s);
g.drawString(s,
textOffset,
startY + myEditor.getAscent());
g.drawString(s,
textOffset,
startY + myEditor.getAscent());
}
}
visLinesIterator.advance();
}
}
finally {
......@@ -952,10 +954,10 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int startOffset = highlighter.getStartOffset();
int endOffset = highlighter.getEndOffset();
int startY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(startOffset)).y;
int startY = myEditor.visibleLineToY(myEditor.offsetToVisualLine(startOffset));
// top edge of the last line of the highlighted area
int endY = myEditor.visualPositionToXY(myEditor.offsetToVisualPosition(endOffset)).y;
int endY = myEditor.visibleLineToY(myEditor.offsetToVisualLine(endOffset));
// => add one line height to make height correct (bottom edge of the highlighted area)
DocumentEx document = myEditor.getDocument();
if (document.getLineStartOffset(document.getLineNumber(endOffset)) != endOffset) {
......@@ -1445,10 +1447,14 @@ class EditorGutterComponentImpl extends EditorGutterComponentEx implements Mouse
int anchorX = getFoldingAreaOffset();
int anchorWidth = getFoldingAnchorWidth();
int neighbourhoodStartOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y - myEditor.getLineHeight())));
int neighbourhoodEndOffset = myEditor.logicalPositionToOffset(myEditor.xyToLogicalPosition(new Point(0, y + myEditor.getLineHeight())));
int visualLine = myEditor.yToVisibleLine(y);
int neighbourhoodStartOffset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(visualLine, 0)));
int neighbourhoodEndOffset = myEditor.logicalPositionToOffset(myEditor.visualToLogicalPosition(new VisualPosition(visualLine,
Integer.MAX_VALUE)));
Collection<DisplayedFoldingAnchor> displayedAnchors = myAnchorsDisplayStrategy.getAnchorsToDisplay(neighbourhoodStartOffset, neighbourhoodEndOffset, null);
Collection<DisplayedFoldingAnchor> displayedAnchors = myAnchorsDisplayStrategy.getAnchorsToDisplay(neighbourhoodStartOffset,
neighbourhoodEndOffset,
null);
for (DisplayedFoldingAnchor anchor : displayedAnchors) {
if (rectangleByFoldOffset(anchor.visualLine, anchorWidth, anchorX).contains(convertX(x), y)) return anchor.foldRegion;
}
......
Supports Markdown
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment