mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-09-27 10:03:11 +07:00
Zero-latency typing: implementation extracted in a separate class
This commit is contained in:
@@ -61,50 +61,5 @@ class DelayMeter {
|
||||
double getStandardDeviation() {
|
||||
return myStats.getStandardDeviation() / 1000000.0D;
|
||||
}
|
||||
|
||||
|
||||
private static class SummaryStatistics {
|
||||
private int myCount = 0;
|
||||
|
||||
private double myMin = Double.MAX_VALUE;
|
||||
|
||||
private double myMax = 0.0D;
|
||||
|
||||
private double myMean = 0.0D;
|
||||
|
||||
private double myS = 0.0D;
|
||||
|
||||
void accept(double value) {
|
||||
myCount++;
|
||||
|
||||
myMin = Math.min(myMin, value);
|
||||
|
||||
myMax = Math.max(myMax, value);
|
||||
|
||||
if (myCount == 1) {
|
||||
myMean = value;
|
||||
} else {
|
||||
double previousMean = myMean;
|
||||
myMean += (value - myMean) / myCount;
|
||||
myS += (value - previousMean) * (value - myMean);
|
||||
}
|
||||
}
|
||||
|
||||
double getMin() {
|
||||
return myMin == Double.MAX_VALUE ? 0.0D : myMin;
|
||||
}
|
||||
|
||||
double getMax() {
|
||||
return myMax;
|
||||
}
|
||||
|
||||
double getMean() {
|
||||
return myMean;
|
||||
}
|
||||
|
||||
double getStandardDeviation() {
|
||||
return Math.sqrt(myS / (myCount - 1));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -25,15 +25,11 @@ import com.intellij.diagnostic.Dumpable;
|
||||
import com.intellij.diagnostic.LogMessageEx;
|
||||
import com.intellij.ide.*;
|
||||
import com.intellij.ide.dnd.DnDManager;
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor;
|
||||
import com.intellij.ide.plugins.PluginManager;
|
||||
import com.intellij.ide.ui.UISettings;
|
||||
import com.intellij.ide.ui.customization.CustomActionsSchema;
|
||||
import com.intellij.notification.*;
|
||||
import com.intellij.openapi.Disposable;
|
||||
import com.intellij.openapi.actionSystem.*;
|
||||
import com.intellij.openapi.actionSystem.ex.ActionManagerEx;
|
||||
import com.intellij.openapi.actionSystem.ex.AnActionListener;
|
||||
import com.intellij.openapi.actionSystem.impl.MouseGestureManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.application.ModalityState;
|
||||
@@ -42,15 +38,12 @@ import com.intellij.openapi.command.UndoConfirmationPolicy;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.*;
|
||||
import com.intellij.openapi.editor.actionSystem.*;
|
||||
import com.intellij.openapi.editor.actions.*;
|
||||
import com.intellij.openapi.editor.colors.*;
|
||||
import com.intellij.openapi.editor.colors.impl.DelegateColorScheme;
|
||||
import com.intellij.openapi.editor.event.*;
|
||||
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.ex.util.EmptyEditorHighlighter;
|
||||
import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter;
|
||||
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
|
||||
import com.intellij.openapi.editor.highlighter.HighlighterClient;
|
||||
import com.intellij.openapi.editor.impl.event.MarkupModelListener;
|
||||
@@ -58,7 +51,6 @@ import com.intellij.openapi.editor.impl.softwrap.SoftWrapAppliancePlaces;
|
||||
import com.intellij.openapi.editor.impl.softwrap.SoftWrapDrawingType;
|
||||
import com.intellij.openapi.editor.impl.view.EditorView;
|
||||
import com.intellij.openapi.editor.markup.*;
|
||||
import com.intellij.openapi.extensions.PluginId;
|
||||
import com.intellij.openapi.fileEditor.FileDocumentManager;
|
||||
import com.intellij.openapi.fileEditor.ex.IdeDocumentHistory;
|
||||
import com.intellij.openapi.fileEditor.impl.EditorsSplitters;
|
||||
@@ -324,41 +316,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
private String myContextMenuGroupId = IdeActions.GROUP_BASIC_EDITOR_POPUP;
|
||||
|
||||
// Characters that excluded from zero-latency painting after key typing
|
||||
private static final Set<Character> KEY_CHARS_TO_SKIP = new HashSet<Character>(Arrays.asList('\n', '\t', '(', ')', '[', ']', '{', '}', '"', '\''));
|
||||
|
||||
// Characters that excluded from zero-latency painting after document update
|
||||
private static final Set<Character> DOCUMENT_CHARS_TO_SKIP = new HashSet<Character>(Arrays.asList(')', ']', '}', '"', '\''));
|
||||
|
||||
// Although it's possible to paint arbitrary line changes immediately,
|
||||
// our primary interest is direct user editing actions, where visual delay is crucial.
|
||||
// Moreover, as many subsystems (like PsiToDocumentSynchronizer, UndoManager, etc.) don't enforce bulk document updates,
|
||||
// and can trigger multiple write actions / document changes sequentially, we need to avoid possible flickering during such an activity.
|
||||
// There seems to be no other way to determine whether particular document change is triggered by direct user editing
|
||||
// (raw character typing is handled separately, even before write action).
|
||||
private static final Set<Class> IMMEDIATE_EDITING_ACTIONS = new HashSet<Class>(Arrays.asList(BackspaceAction.class,
|
||||
DeleteAction.class,
|
||||
DeleteToWordStartAction.class,
|
||||
DeleteToWordEndAction.class,
|
||||
DeleteToWordStartInDifferentHumpsModeAction.class,
|
||||
DeleteToWordEndInDifferentHumpsModeAction.class,
|
||||
DeleteToLineStartAction.class,
|
||||
DeleteToLineEndAction.class,
|
||||
CutAction.class,
|
||||
PasteAction.class));
|
||||
|
||||
private Rectangle myOldArea = new Rectangle(0, 0, 0, 0);
|
||||
private Rectangle myOldTailArea = new Rectangle(0, 0, 0, 0);
|
||||
private boolean myImmediateEditingInProgress;
|
||||
|
||||
// TODO Should be removed when IDEA adopts typing without starting write actions.
|
||||
private static final boolean VIM_PLUGIN_LOADED = isPluginLoaded("IdeaVIM");
|
||||
|
||||
private static final int TYPING_STATS_SAMPLE_SIZE = 50;
|
||||
|
||||
private int myCharsTyped;
|
||||
private final DelayMeter myTypingLatencyMeter = new DelayMeter();
|
||||
private boolean myZeroLatencyTypingWasEnabled = isZeroLatencyTypingEnabled();
|
||||
private final ImmediatePainter myImmediatePainter;
|
||||
|
||||
static {
|
||||
ourCaretBlinkingCommand.start();
|
||||
@@ -391,6 +349,8 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
myCommandProcessor = CommandProcessor.getInstance();
|
||||
|
||||
myImmediatePainter = new ImmediatePainterImpl(this);
|
||||
|
||||
if (project != null) {
|
||||
myConnection = project.getMessageBus().connect();
|
||||
myConnection.subscribe(DocumentBulkUpdateListener.TOPIC, new EditorDocumentBulkUpdateAdapter());
|
||||
@@ -996,25 +956,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
myPanel.add(myScrollPane);
|
||||
}
|
||||
|
||||
AnActionListener.Adapter actionListener = new AnActionListener.Adapter() {
|
||||
@Override
|
||||
public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
|
||||
if (isZeroLatencyTypingEnabled() && IMMEDIATE_EDITING_ACTIONS.contains(action.getClass()) &&
|
||||
!(action.getClass() == PasteAction.class && mySelectionModel.hasSelection())) {
|
||||
myImmediateEditingInProgress = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
|
||||
if (isZeroLatencyTypingEnabled()) {
|
||||
myImmediateEditingInProgress = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ActionManager.getInstance().addAnActionListener(actionListener, myDisposable);
|
||||
|
||||
myEditorComponent.addKeyListener(new KeyListener() {
|
||||
@Override
|
||||
public void keyPressed(@NotNull KeyEvent e) {
|
||||
@@ -1198,62 +1139,15 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
ActionManagerEx actionManager = ActionManagerEx.getInstanceEx();
|
||||
DataContext dataContext = getDataContext();
|
||||
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
if (myZeroLatencyTypingWasEnabled != isZeroLatencyTypingEnabled()) {
|
||||
resetTypingLatencyStats();
|
||||
myZeroLatencyTypingWasEnabled = isZeroLatencyTypingEnabled();
|
||||
}
|
||||
myTypingLatencyMeter.registerStart();
|
||||
}
|
||||
|
||||
if (isZeroLatencyTypingEnabled() && myDocument.isWritable() && !isViewer() && canPaintImmediately(c)) {
|
||||
for (Caret caret : myCaretModel.getAllCarets()) {
|
||||
paintImmediately(caret.getOffset(), c, myIsInsertMode);
|
||||
}
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
// not all chars are painted immediately, so type usual letters / digits to compute precise stats
|
||||
myTypingLatencyMeter.registerFinish();
|
||||
}
|
||||
}
|
||||
myImmediatePainter.paintCharacter(myEditorComponent.getGraphics(), c);
|
||||
|
||||
actionManager.fireBeforeEditorTyping(c, dataContext);
|
||||
MacUIUtil.hideCursor();
|
||||
EditorActionManager.getInstance().getTypedAction().actionPerformed(this, c, dataContext);
|
||||
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
myCharsTyped++;
|
||||
|
||||
if (myCharsTyped == TYPING_STATS_SAMPLE_SIZE) {
|
||||
String stats = formatTypingLatencyStats(myTypingLatencyMeter);
|
||||
printToEventLog(stats);
|
||||
LOG.info(stats);
|
||||
|
||||
resetTypingLatencyStats();
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private static String formatTypingLatencyStats(DelayMeter meter) {
|
||||
return String.format("Zero-latency: %3s; typing delay, ms: min: %5.1f | max: %5.1f | avg: %5.1f | sigma: %4.1f",
|
||||
isZeroLatencyTypingEnabled() ? "on" : "off",
|
||||
meter.getMin(), meter.getMax(), meter.getMean(), meter.getStandardDeviation());
|
||||
}
|
||||
|
||||
private void printToEventLog(String message) {
|
||||
NotificationGroup group = NotificationGroup.logOnlyGroup("typing-delay-stats");
|
||||
Notification notification = group.createNotification(message, NotificationType.INFORMATION);
|
||||
notification.setImportant(true);
|
||||
notification.notify(myProject);
|
||||
notification.hideBalloon();
|
||||
}
|
||||
|
||||
private void resetTypingLatencyStats() {
|
||||
myCharsTyped = 0;
|
||||
myTypingLatencyMeter.reset();
|
||||
}
|
||||
|
||||
private void fireFocusLost() {
|
||||
for (FocusChangeListener listener : myFocusListeners) {
|
||||
listener.focusLost(this);
|
||||
@@ -1994,26 +1888,14 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
mySizeContainer.beforeChange(e);
|
||||
}
|
||||
|
||||
if (isZeroLatencyTypingEnabled() && myImmediateEditingInProgress && canPaintImmediately(e)) {
|
||||
int offset = e.getOffset();
|
||||
int length = e.getOldLength();
|
||||
|
||||
myOldArea = lineRectangleBetween(offset, offset + length);
|
||||
|
||||
myOldTailArea = lineRectangleBetween(offset + length, myDocument.getLineEndOffset(myDocument.getLineNumber(offset)));
|
||||
if (myOldTailArea.isEmpty()) {
|
||||
myOldTailArea.width += EditorUtil.getSpaceWidth(Font.PLAIN, this); // include possible caret
|
||||
}
|
||||
}
|
||||
myImmediatePainter.beforeUpdate(e);
|
||||
}
|
||||
|
||||
private void changedUpdate(DocumentEvent e) {
|
||||
myDocumentChangeInProgress = false;
|
||||
if (myDocument.isInBulkUpdate()) return;
|
||||
|
||||
if (isZeroLatencyTypingEnabled() && myImmediateEditingInProgress && canPaintImmediately(e)) {
|
||||
paintImmediately(e);
|
||||
}
|
||||
myImmediatePainter.paintUpdate(myEditorComponent.getGraphics(), e);
|
||||
|
||||
if (myErrorStripeNeedsRepaint) {
|
||||
myMarkupModel.repaint(e.getOffset(), e.getOffset() + e.getNewLength());
|
||||
@@ -2228,219 +2110,6 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
putUserData(BUFFER, image);
|
||||
}
|
||||
|
||||
private static boolean isZeroLatencyTypingEnabled() {
|
||||
// Zero-latency typing is suppressed when Idea VIM plugin is loaded, because of VIM-1007.
|
||||
// That issue will be resolved automatically when IDEA adopts typing without starting write actions.
|
||||
return !VIM_PLUGIN_LOADED && Registry.is("editor.zero.latency.typing");
|
||||
}
|
||||
|
||||
private static boolean isTypingLatencyStatsEnabled() {
|
||||
return Registry.is("editor.typing.latency.stats");
|
||||
}
|
||||
|
||||
private static boolean isPluginLoaded(@NotNull String id) {
|
||||
PluginId pluginId = PluginId.findId(id);
|
||||
if (pluginId == null) return false;
|
||||
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
|
||||
if (plugin == null) return false;
|
||||
return plugin.isEnabled();
|
||||
}
|
||||
|
||||
private boolean canPaintImmediately(char c) {
|
||||
return myDocument instanceof DocumentImpl &&
|
||||
myHighlighter instanceof LexerEditorHighlighter &&
|
||||
!mySelectionModel.hasSelection() &&
|
||||
arePositionsWithinDocument(myCaretModel.getAllCarets()) &&
|
||||
areVisualLinesUnique(myCaretModel.getAllCarets()) &&
|
||||
!isInplaceRenamerActive() &&
|
||||
!KEY_CHARS_TO_SKIP.contains(c);
|
||||
}
|
||||
|
||||
private static boolean areVisualLinesUnique(List<Caret> carets) {
|
||||
if (carets.size() > 1) {
|
||||
TIntHashSet lines = new TIntHashSet(carets.size());
|
||||
for (Caret caret : carets) {
|
||||
if (!lines.add(caret.getVisualLineStart())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks whether all the carets are within document or some of them are in a so called "virtual space".
|
||||
private boolean arePositionsWithinDocument(List<Caret> carets) {
|
||||
for (Caret caret : carets) {
|
||||
if (caret.getLogicalPosition().compareTo(offsetToLogicalPosition(caret.getOffset())) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Improve the approach - handle such cases in a more general way.
|
||||
private boolean isInplaceRenamerActive() {
|
||||
Key<?> key = Key.findKeyByName("EditorInplaceRenamer");
|
||||
return key != null && key.isIn(this);
|
||||
}
|
||||
|
||||
// Called to display a single character insertion before starting a write action and the general painting routine.
|
||||
// Bypasses RepaintManager (c.repaint, c.paintComponent) and double buffering (g.paintImmediately) to minimize visual lag.
|
||||
// TODO Should be replaced with the generic paintImmediately(event) call when we implement typing without starting write actions.
|
||||
private void paintImmediately(int offset, char c, boolean insert) {
|
||||
Graphics g = myEditorComponent.getGraphics();
|
||||
|
||||
if (g == null) return; // editor component is currently not displayable
|
||||
|
||||
TextAttributes attributes = ((LexerEditorHighlighter)myHighlighter).getAttributes((DocumentImpl)myDocument, offset, c);
|
||||
|
||||
int fontType = attributes.getFontType();
|
||||
FontInfo fontInfo = EditorUtil.fontForChar(c, attributes.getFontType(), this);
|
||||
Font font = fontInfo.getFont();
|
||||
|
||||
// it's more reliable to query actual font metrics
|
||||
FontMetrics fontMetrics = getFontMetrics(fontType);
|
||||
|
||||
int charWidth = fontMetrics.charWidth(c);
|
||||
|
||||
int delta = charWidth;
|
||||
|
||||
if (!insert && offset < myDocument.getTextLength()) {
|
||||
delta -= fontMetrics.charWidth(myDocument.getCharsSequence().charAt(offset));
|
||||
}
|
||||
|
||||
Rectangle tailArea = lineRectangleBetween(offset, myDocument.getLineEndOffset(offsetToLogicalLine(offset)));
|
||||
if (tailArea.isEmpty()) {
|
||||
tailArea.width += EditorUtil.getSpaceWidth(fontType, this); // include caret
|
||||
}
|
||||
|
||||
Color background = attributes.getBackgroundColor() == null ? getCaretRowBackground() : attributes.getBackgroundColor();
|
||||
|
||||
Rectangle newArea = lineRectangleBetween(offset, offset);
|
||||
newArea.width += charWidth;
|
||||
|
||||
String newText = Character.toString(c);
|
||||
Point point = newArea.getLocation();
|
||||
int ascent = getAscent();
|
||||
Color foreground = attributes.getForegroundColor() == null ? getForegroundColor() : attributes.getForegroundColor();
|
||||
|
||||
EditorUIUtil.setupAntialiasing(g);
|
||||
|
||||
// pre-compute all the arguments beforehand to minimize delays between the calls (as there's no double-buffering)
|
||||
if (delta != 0) {
|
||||
shift(g, tailArea, delta);
|
||||
}
|
||||
fill(g, newArea, background);
|
||||
print(g, newText, point, ascent, font, foreground);
|
||||
|
||||
// flush changes (there can be batching / buffering in video driver)
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
private boolean canPaintImmediately(@NotNull DocumentEvent e) {
|
||||
return myDocument instanceof DocumentImpl &&
|
||||
!isInplaceRenamerActive() &&
|
||||
StringUtil.indexOf(e.getOldFragment(), '\n') == -1 &&
|
||||
StringUtil.indexOf(e.getNewFragment(), '\n') == -1 &&
|
||||
!(e.getNewLength() == 1 && DOCUMENT_CHARS_TO_SKIP.contains(e.getNewFragment().charAt(0)));
|
||||
}
|
||||
|
||||
// Called to display insertion / deletion / replacement within a single line before the general painting routine.
|
||||
// Bypasses RepaintManager (c.repaint, c.paintComponent) and double buffering (g.paintImmediately) to minimize visual lag.
|
||||
private void paintImmediately(@NotNull DocumentEvent e) {
|
||||
Graphics g = myEditorComponent.getGraphics();
|
||||
|
||||
if (g == null) return; // editor component is currently not displayable
|
||||
|
||||
int offset = e.getOffset();
|
||||
String newText = e.getNewFragment().toString();
|
||||
Rectangle newArea = lineRectangleBetween(offset, offset + newText.length());
|
||||
int delta = newArea.width - myOldArea.width;
|
||||
Color background = getCaretRowBackground();
|
||||
|
||||
if (delta != 0) {
|
||||
if (delta < 0) {
|
||||
// Pre-paint carets at new positions, if needed, before shifting the tail area (to avoid flickering),
|
||||
// because painting takes some time while copyArea is almost instantaneous.
|
||||
CaretRectangle[] caretRectangles = myCaretCursor.getCaretLocations(true);
|
||||
if (caretRectangles != null) {
|
||||
for (CaretRectangle it : caretRectangles) {
|
||||
Rectangle r = toRectangle(it);
|
||||
if (myOldArea.contains(r) && !newArea.contains(r)) {
|
||||
myCaretCursor.paintAt(g, it.myPoint.x - delta, it.myPoint.y, it.myWidth, it.myCaret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shift(g, myOldTailArea, delta);
|
||||
|
||||
if (delta < 0) {
|
||||
Rectangle remainingArea = new Rectangle(myOldTailArea.x + myOldTailArea.width + delta,
|
||||
myOldTailArea.y, -delta, myOldTailArea.height);
|
||||
fill(g, remainingArea, background);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newArea.isEmpty()) {
|
||||
TextAttributes attributes = myHighlighter.createIterator(offset).getTextAttributes();
|
||||
|
||||
Point point = newArea.getLocation();
|
||||
int ascent = getAscent();
|
||||
// simplified font selection (based on the first character)
|
||||
FontInfo fontInfo = EditorUtil.fontForChar(newText.charAt(0), attributes.getFontType(), this);
|
||||
Font font = fontInfo.getFont();
|
||||
|
||||
Color foreground = attributes.getForegroundColor() == null ? getForegroundColor() : attributes.getForegroundColor();
|
||||
|
||||
EditorUIUtil.setupAntialiasing(g);
|
||||
|
||||
// pre-compute all the arguments beforehand to minimize delay between the calls (as there's no double-buffering)
|
||||
fill(g, newArea, background);
|
||||
print(g, newText, point, ascent, font, foreground);
|
||||
}
|
||||
|
||||
// flush changes (there can be batching / buffering in video driver)
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Rectangle lineRectangleBetween(int begin, int end) {
|
||||
Point p1 = offsetToXY(begin, false);
|
||||
Point p2 = offsetToXY(end, false);
|
||||
// When soft wrap is present, handle only the first visual line (for simplicity, yet it works reasonably well)
|
||||
int x2 = p1.y == p2.y ? p2.x : Math.max(p1.x, myEditorComponent.getWidth() - getVerticalScrollBar().getWidth());
|
||||
return new Rectangle(p1.x, p1.y, x2 - p1.x, getLineHeight());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Rectangle toRectangle(@NotNull CaretRectangle caretRectangle) {
|
||||
Point p = caretRectangle.myPoint;
|
||||
return new Rectangle(p.x, p.y, caretRectangle.myWidth, getLineHeight());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Color getCaretRowBackground() {
|
||||
Color color = myScheme.getColor(EditorColors.CARET_ROW_COLOR);
|
||||
return color == null ? getBackgroundColor() : color;
|
||||
}
|
||||
|
||||
private static void shift(@NotNull Graphics g, @NotNull Rectangle r, int delta) {
|
||||
g.copyArea(r.x, r.y, r.width, r.height, delta, 0);
|
||||
}
|
||||
|
||||
private static void fill(@NotNull Graphics g, @NotNull Rectangle r, @NotNull Color color) {
|
||||
g.setColor(color);
|
||||
g.fillRect(r.x, r.y, r.width, r.height);
|
||||
}
|
||||
|
||||
private static void print(@NotNull Graphics g, @NotNull String text, @NotNull Point point,
|
||||
int ascent, @NotNull Font font, @NotNull Color color) {
|
||||
g.setFont(font);
|
||||
g.setColor(color);
|
||||
g.drawString(text, point.x, point.y + ascent);
|
||||
}
|
||||
|
||||
void paint(@NotNull Graphics2D g) {
|
||||
Rectangle clip = g.getClipBounds();
|
||||
|
||||
@@ -2501,9 +2170,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
paintComposedTextDecoration(g);
|
||||
}
|
||||
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
myTypingLatencyMeter.registerFinish();
|
||||
}
|
||||
myImmediatePainter.afterPainting();
|
||||
}
|
||||
|
||||
private static final char IDEOGRAPHIC_SPACE = '\u3000'; // http://www.marathon-studios.com/unicode/U3000/Ideographic_Space
|
||||
@@ -2579,7 +2246,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Color getForegroundColor() {
|
||||
Color getForegroundColor() {
|
||||
return myScheme.getDefaultForeground();
|
||||
}
|
||||
|
||||
@@ -3581,6 +3248,11 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
|
||||
myCaretCursor.paint(g);
|
||||
}
|
||||
|
||||
@NotNull
|
||||
CaretCursor getCaretCursor() {
|
||||
return myCaretCursor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CaretRectangle[] getCaretLocations(boolean onlyIfShown) {
|
||||
@@ -5092,7 +4764,7 @@ public final class EditorImpl extends UserDataHolderBase implements EditorEx, Hi
|
||||
}
|
||||
}
|
||||
|
||||
private class CaretCursor {
|
||||
class CaretCursor {
|
||||
private CaretRectangle[] myLocations;
|
||||
private boolean myEnabled;
|
||||
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.impl;
|
||||
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
|
||||
/**
|
||||
* @author Pavel Fatin
|
||||
*/
|
||||
interface ImmediatePainter {
|
||||
void paintCharacter(Graphics g, char c);
|
||||
|
||||
void beforeUpdate(@NotNull DocumentEvent e);
|
||||
|
||||
void paintUpdate(Graphics g, @NotNull DocumentEvent e);
|
||||
|
||||
void afterPainting();
|
||||
}
|
||||
+436
@@ -0,0 +1,436 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.impl;
|
||||
|
||||
import com.intellij.ide.plugins.IdeaPluginDescriptor;
|
||||
import com.intellij.ide.plugins.PluginManager;
|
||||
import com.intellij.notification.Notification;
|
||||
import com.intellij.notification.NotificationGroup;
|
||||
import com.intellij.notification.NotificationType;
|
||||
import com.intellij.openapi.actionSystem.ActionManager;
|
||||
import com.intellij.openapi.actionSystem.AnAction;
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.actionSystem.ex.AnActionListener;
|
||||
import com.intellij.openapi.editor.Caret;
|
||||
import com.intellij.openapi.editor.CaretModel;
|
||||
import com.intellij.openapi.editor.Document;
|
||||
import com.intellij.openapi.editor.SelectionModel;
|
||||
import com.intellij.openapi.editor.actions.*;
|
||||
import com.intellij.openapi.editor.colors.EditorColors;
|
||||
import com.intellij.openapi.editor.colors.EditorColorsScheme;
|
||||
import com.intellij.openapi.editor.event.DocumentEvent;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUIUtil;
|
||||
import com.intellij.openapi.editor.ex.util.EditorUtil;
|
||||
import com.intellij.openapi.editor.ex.util.LexerEditorHighlighter;
|
||||
import com.intellij.openapi.editor.highlighter.EditorHighlighter;
|
||||
import com.intellij.openapi.editor.markup.TextAttributes;
|
||||
import com.intellij.openapi.extensions.PluginId;
|
||||
import com.intellij.openapi.util.Key;
|
||||
import com.intellij.openapi.util.registry.Registry;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import gnu.trove.TIntHashSet;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
* @author Pavel Fatin
|
||||
*/
|
||||
class ImmediatePainterImpl implements ImmediatePainter {
|
||||
// Characters that excluded from zero-latency painting after key typing
|
||||
private static final Set<Character> KEY_CHARS_TO_SKIP =
|
||||
new HashSet<Character>(Arrays.asList('\n', '\t', '(', ')', '[', ']', '{', '}', '"', '\''));
|
||||
|
||||
// Characters that excluded from zero-latency painting after document update
|
||||
private static final Set<Character> DOCUMENT_CHARS_TO_SKIP =
|
||||
new HashSet<Character>(Arrays.asList(')', ']', '}', '"', '\''));
|
||||
|
||||
// Although it's possible to paint arbitrary line changes immediately,
|
||||
// our primary interest is direct user editing actions, where visual delay is crucial.
|
||||
// Moreover, as many subsystems (like PsiToDocumentSynchronizer, UndoManager, etc.) don't enforce bulk document updates,
|
||||
// and can trigger multiple write actions / document changes sequentially, we need to avoid possible flickering during such an activity.
|
||||
// There seems to be no other way to determine whether particular document change is triggered by direct user editing
|
||||
// (raw character typing is handled separately, even before write action).
|
||||
private static final Set<Class> IMMEDIATE_EDITING_ACTIONS = new HashSet<Class>(Arrays.asList(BackspaceAction.class,
|
||||
DeleteAction.class,
|
||||
DeleteToWordStartAction.class,
|
||||
DeleteToWordEndAction.class,
|
||||
DeleteToWordStartInDifferentHumpsModeAction.class,
|
||||
DeleteToWordEndInDifferentHumpsModeAction.class,
|
||||
DeleteToLineStartAction.class,
|
||||
DeleteToLineEndAction.class,
|
||||
CutAction.class,
|
||||
PasteAction.class));
|
||||
public static final String ZERO_LATENCY_TYPING_KEY = "editor.zero.latency.typing";
|
||||
|
||||
public static final String TYPING_LATENCY_STATS_KEY = "editor.typing.latency.stats";
|
||||
|
||||
private static final int TYPING_STATS_SAMPLE_SIZE = 50;
|
||||
|
||||
// TODO Should be removed when IDEA adopts typing without starting write actions.
|
||||
private static final boolean VIM_PLUGIN_LOADED = isPluginLoaded("IdeaVIM");
|
||||
|
||||
private Rectangle myOldArea = new Rectangle(0, 0, 0, 0);
|
||||
private Rectangle myOldTailArea = new Rectangle(0, 0, 0, 0);
|
||||
private boolean myImmediateEditingInProgress;
|
||||
|
||||
private final EditorImpl myEditor;
|
||||
|
||||
private int myCharsTyped;
|
||||
private final DelayMeter myTypingLatencyMeter = new DelayMeter();
|
||||
private boolean myZeroLatencyTypingWasEnabled = isZeroLatencyTypingEnabled();
|
||||
|
||||
|
||||
ImmediatePainterImpl(EditorImpl editor) {
|
||||
myEditor = editor;
|
||||
AnActionListener.Adapter actionListener = new AnActionListener.Adapter() {
|
||||
@Override
|
||||
public void beforeActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
|
||||
if (isZeroLatencyTypingEnabled() && IMMEDIATE_EDITING_ACTIONS.contains(action.getClass()) &&
|
||||
!(action.getClass() == PasteAction.class && getSelectionModel().hasSelection())) {
|
||||
myImmediateEditingInProgress = true;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterActionPerformed(AnAction action, DataContext dataContext, AnActionEvent event) {
|
||||
if (isZeroLatencyTypingEnabled()) {
|
||||
myImmediateEditingInProgress = false;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
ActionManager.getInstance().addAnActionListener(actionListener, editor.getDisposable());
|
||||
}
|
||||
|
||||
protected Document getDocument() {
|
||||
return myEditor.getDocument();
|
||||
}
|
||||
|
||||
protected SelectionModel getSelectionModel() {
|
||||
return myEditor.getSelectionModel();
|
||||
}
|
||||
|
||||
protected EditorHighlighter getHighlighter() {
|
||||
return myEditor.getHighlighter();
|
||||
}
|
||||
|
||||
protected CaretModel getCaretModel() {
|
||||
return myEditor.getCaretModel();
|
||||
}
|
||||
|
||||
protected EditorColorsScheme getColorsScheme() {
|
||||
return myEditor.getColorsScheme();
|
||||
}
|
||||
|
||||
protected EditorComponentImpl getContentComponent() {
|
||||
return myEditor.getContentComponent();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintCharacter(Graphics g, char c) {
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
if (myZeroLatencyTypingWasEnabled != isZeroLatencyTypingEnabled()) {
|
||||
resetTypingLatencyStats();
|
||||
myZeroLatencyTypingWasEnabled = isZeroLatencyTypingEnabled();
|
||||
}
|
||||
myTypingLatencyMeter.registerStart();
|
||||
}
|
||||
|
||||
if (isZeroLatencyTypingEnabled() && getDocument().isWritable() && !myEditor.isViewer() && canPaintImmediately(c)) {
|
||||
for (Caret caret : getCaretModel().getAllCarets()) {
|
||||
paintImmediately(g, caret.getOffset(), c, myEditor.isInsertMode());
|
||||
}
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
// not all chars are painted immediately, so type usual letters / digits to compute precise stats
|
||||
myTypingLatencyMeter.registerFinish();
|
||||
}
|
||||
}
|
||||
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
myCharsTyped++;
|
||||
|
||||
if (myCharsTyped == TYPING_STATS_SAMPLE_SIZE) {
|
||||
String stats = formatTypingLatencyStats(myTypingLatencyMeter);
|
||||
printToEventLog(stats);
|
||||
|
||||
resetTypingLatencyStats();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static String formatTypingLatencyStats(DelayMeter meter) {
|
||||
return String.format("Zero-latency: %3s; typing delay, ms: min: %5.1f | max: %5.1f | avg: %5.1f | sigma: %4.1f",
|
||||
isZeroLatencyTypingEnabled() ? "on" : "off",
|
||||
meter.getMin(), meter.getMax(), meter.getMean(), meter.getStandardDeviation());
|
||||
}
|
||||
|
||||
private void printToEventLog(String message) {
|
||||
NotificationGroup group = NotificationGroup.logOnlyGroup("typing-delay-stats");
|
||||
Notification notification = group.createNotification(message, NotificationType.INFORMATION);
|
||||
notification.setImportant(true);
|
||||
notification.notify(myEditor.getProject());
|
||||
notification.hideBalloon();
|
||||
}
|
||||
|
||||
private void resetTypingLatencyStats() {
|
||||
myCharsTyped = 0;
|
||||
myTypingLatencyMeter.reset();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPainting() {
|
||||
if (isTypingLatencyStatsEnabled()) {
|
||||
myTypingLatencyMeter.registerFinish();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void beforeUpdate(@NotNull DocumentEvent e) {
|
||||
if (isZeroLatencyTypingEnabled() && myImmediateEditingInProgress && canPaintImmediately(e)) {
|
||||
int offset = e.getOffset();
|
||||
int length = e.getOldLength();
|
||||
|
||||
myOldArea = lineRectangleBetween(offset, offset + length);
|
||||
|
||||
myOldTailArea = lineRectangleBetween(offset + length, getDocument().getLineEndOffset(getDocument().getLineNumber(offset)));
|
||||
if (myOldTailArea.isEmpty()) {
|
||||
myOldTailArea.width += EditorUtil.getSpaceWidth(Font.PLAIN, myEditor); // include possible caret
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void paintUpdate(Graphics g, @NotNull DocumentEvent e) {
|
||||
if (isZeroLatencyTypingEnabled() && myImmediateEditingInProgress && canPaintImmediately(e)) {
|
||||
paintImmediately(g, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean isZeroLatencyTypingEnabled() {
|
||||
// Zero-latency typing is suppressed when Idea VIM plugin is loaded, because of VIM-1007.
|
||||
// That issue will be resolved automatically when IDEA adopts typing without starting write actions.
|
||||
return !VIM_PLUGIN_LOADED && Registry.is(ZERO_LATENCY_TYPING_KEY);
|
||||
}
|
||||
|
||||
private static boolean isTypingLatencyStatsEnabled() {
|
||||
return Registry.is(TYPING_LATENCY_STATS_KEY);
|
||||
}
|
||||
|
||||
private static boolean isPluginLoaded(@NotNull String id) {
|
||||
PluginId pluginId = PluginId.findId(id);
|
||||
if (pluginId == null) return false;
|
||||
IdeaPluginDescriptor plugin = PluginManager.getPlugin(pluginId);
|
||||
if (plugin == null) return false;
|
||||
return plugin.isEnabled();
|
||||
}
|
||||
|
||||
private boolean canPaintImmediately(char c) {
|
||||
return getDocument() instanceof DocumentImpl &&
|
||||
getHighlighter() instanceof LexerEditorHighlighter &&
|
||||
!getSelectionModel().hasSelection() &&
|
||||
arePositionsWithinDocument(getCaretModel().getAllCarets()) &&
|
||||
areVisualLinesUnique(getCaretModel().getAllCarets()) &&
|
||||
!isInplaceRenamerActive() &&
|
||||
!KEY_CHARS_TO_SKIP.contains(c);
|
||||
}
|
||||
|
||||
private static boolean areVisualLinesUnique(List<Caret> carets) {
|
||||
if (carets.size() > 1) {
|
||||
TIntHashSet lines = new TIntHashSet(carets.size());
|
||||
for (Caret caret : carets) {
|
||||
if (!lines.add(caret.getVisualLineStart())) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Checks whether all the carets are within document or some of them are in a so called "virtual space".
|
||||
private boolean arePositionsWithinDocument(List<Caret> carets) {
|
||||
for (Caret caret : carets) {
|
||||
if (caret.getLogicalPosition().compareTo(myEditor.offsetToLogicalPosition(caret.getOffset())) != 0) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// TODO Improve the approach - handle such cases in a more general way.
|
||||
private boolean isInplaceRenamerActive() {
|
||||
Key<?> key = Key.findKeyByName("EditorInplaceRenamer");
|
||||
return key != null && key.isIn(myEditor);
|
||||
}
|
||||
|
||||
// Called to display a single character insertion before starting a write action and the general painting routine.
|
||||
// Bypasses RepaintManager (c.repaint, c.paintComponent) and double buffering (g.paintImmediately) to minimize visual lag.
|
||||
// TODO Should be replaced with the generic paintImmediately(event) call when we implement typing without starting write actions.
|
||||
private void paintImmediately(Graphics g, int offset, char c, boolean insert) {
|
||||
if (g == null) return; // editor component is currently not displayable
|
||||
|
||||
TextAttributes attributes = ((LexerEditorHighlighter)getHighlighter()).getAttributes((DocumentImpl)getDocument(), offset, c);
|
||||
|
||||
int fontType = attributes.getFontType();
|
||||
FontInfo fontInfo = EditorUtil.fontForChar(c, attributes.getFontType(), myEditor);
|
||||
Font font = fontInfo.getFont();
|
||||
|
||||
// it's more reliable to query actual font metrics
|
||||
FontMetrics fontMetrics = myEditor.getFontMetrics(fontType);
|
||||
|
||||
int charWidth = fontMetrics.charWidth(c);
|
||||
|
||||
int delta = charWidth;
|
||||
|
||||
if (!insert && offset < getDocument().getTextLength()) {
|
||||
delta -= fontMetrics.charWidth(getDocument().getCharsSequence().charAt(offset));
|
||||
}
|
||||
|
||||
Rectangle tailArea = lineRectangleBetween(offset, getDocument().getLineEndOffset(myEditor.offsetToLogicalLine(offset)));
|
||||
if (tailArea.isEmpty()) {
|
||||
tailArea.width += EditorUtil.getSpaceWidth(fontType, myEditor); // include caret
|
||||
}
|
||||
|
||||
Color background = attributes.getBackgroundColor() == null ? getCaretRowBackground() : attributes.getBackgroundColor();
|
||||
|
||||
Rectangle newArea = lineRectangleBetween(offset, offset);
|
||||
newArea.width += charWidth;
|
||||
|
||||
String newText = Character.toString(c);
|
||||
Point point = newArea.getLocation();
|
||||
int ascent = myEditor.getAscent();
|
||||
Color foreground = attributes.getForegroundColor() == null ? myEditor.getForegroundColor() : attributes.getForegroundColor();
|
||||
|
||||
EditorUIUtil.setupAntialiasing(g);
|
||||
|
||||
// pre-compute all the arguments beforehand to minimize delays between the calls (as there's no double-buffering)
|
||||
if (delta != 0) {
|
||||
shift(g, tailArea, delta);
|
||||
}
|
||||
fill(g, newArea, background);
|
||||
print(g, newText, point, ascent, font, foreground);
|
||||
|
||||
// flush changes (there can be batching / buffering in video driver)
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
private boolean canPaintImmediately(@NotNull DocumentEvent e) {
|
||||
return getDocument() instanceof DocumentImpl &&
|
||||
!isInplaceRenamerActive() &&
|
||||
StringUtil.indexOf(e.getOldFragment(), '\n') == -1 &&
|
||||
StringUtil.indexOf(e.getNewFragment(), '\n') == -1 &&
|
||||
!(e.getNewLength() == 1 && DOCUMENT_CHARS_TO_SKIP.contains(e.getNewFragment().charAt(0)));
|
||||
}
|
||||
|
||||
// Called to display insertion / deletion / replacement within a single line before the general painting routine.
|
||||
// Bypasses RepaintManager (c.repaint, c.paintComponent) and double buffering (g.paintImmediately) to minimize visual lag.
|
||||
private void paintImmediately(Graphics g, @NotNull DocumentEvent e) {
|
||||
if (g == null) return; // editor component is currently not displayable
|
||||
|
||||
int offset = e.getOffset();
|
||||
String newText = e.getNewFragment().toString();
|
||||
Rectangle newArea = lineRectangleBetween(offset, offset + newText.length());
|
||||
int delta = newArea.width - myOldArea.width;
|
||||
Color background = getCaretRowBackground();
|
||||
|
||||
if (delta != 0) {
|
||||
if (delta < 0) {
|
||||
// Pre-paint carets at new positions, if needed, before shifting the tail area (to avoid flickering),
|
||||
// because painting takes some time while copyArea is almost instantaneous.
|
||||
EditorImpl.CaretRectangle[] caretRectangles = myEditor.getCaretCursor().getCaretLocations(true);
|
||||
if (caretRectangles != null) {
|
||||
for (EditorImpl.CaretRectangle it : caretRectangles) {
|
||||
Rectangle r = toRectangle(it);
|
||||
if (myOldArea.contains(r) && !newArea.contains(r)) {
|
||||
myEditor.getCaretCursor().paintAt(g, it.myPoint.x - delta, it.myPoint.y, it.myWidth, it.myCaret);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
shift(g, myOldTailArea, delta);
|
||||
|
||||
if (delta < 0) {
|
||||
Rectangle remainingArea = new Rectangle(myOldTailArea.x + myOldTailArea.width + delta,
|
||||
myOldTailArea.y, -delta, myOldTailArea.height);
|
||||
fill(g, remainingArea, background);
|
||||
}
|
||||
}
|
||||
|
||||
if (!newArea.isEmpty()) {
|
||||
TextAttributes attributes = getHighlighter().createIterator(offset).getTextAttributes();
|
||||
|
||||
Point point = newArea.getLocation();
|
||||
int ascent = myEditor.getAscent();
|
||||
// simplified font selection (based on the first character)
|
||||
FontInfo fontInfo = EditorUtil.fontForChar(newText.charAt(0), attributes.getFontType(), myEditor);
|
||||
Font font = fontInfo.getFont();
|
||||
|
||||
Color foreground = attributes.getForegroundColor() == null ? myEditor.getForegroundColor() : attributes.getForegroundColor();
|
||||
|
||||
EditorUIUtil.setupAntialiasing(g);
|
||||
|
||||
// pre-compute all the arguments beforehand to minimize delay between the calls (as there's no double-buffering)
|
||||
fill(g, newArea, background);
|
||||
print(g, newText, point, ascent, font, foreground);
|
||||
}
|
||||
|
||||
// flush changes (there can be batching / buffering in video driver)
|
||||
Toolkit.getDefaultToolkit().sync();
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Rectangle lineRectangleBetween(int begin, int end) {
|
||||
Point p1 = myEditor.offsetToXY(begin, false);
|
||||
Point p2 = myEditor.offsetToXY(end, false);
|
||||
// When soft wrap is present, handle only the first visual line (for simplicity, yet it works reasonably well)
|
||||
int x2 = p1.y == p2.y ? p2.x : Math.max(p1.x, getContentComponent().getWidth() - myEditor.getVerticalScrollBar().getWidth());
|
||||
return new Rectangle(p1.x, p1.y, x2 - p1.x, myEditor.getLineHeight());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Rectangle toRectangle(@NotNull EditorImpl.CaretRectangle caretRectangle) {
|
||||
Point p = caretRectangle.myPoint;
|
||||
return new Rectangle(p.x, p.y, caretRectangle.myWidth, myEditor.getLineHeight());
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private Color getCaretRowBackground() {
|
||||
Color color = getColorsScheme().getColor(EditorColors.CARET_ROW_COLOR);
|
||||
return color == null ? myEditor.getBackgroundColor() : color;
|
||||
}
|
||||
|
||||
private static void shift(@NotNull Graphics g, @NotNull Rectangle r, int delta) {
|
||||
g.copyArea(r.x, r.y, r.width, r.height, delta, 0);
|
||||
}
|
||||
|
||||
private static void fill(@NotNull Graphics g, @NotNull Rectangle r, @NotNull Color color) {
|
||||
g.setColor(color);
|
||||
g.fillRect(r.x, r.y, r.width, r.height);
|
||||
}
|
||||
|
||||
private static void print(@NotNull Graphics g, @NotNull String text, @NotNull Point point,
|
||||
int ascent, @NotNull Font font, @NotNull Color color) {
|
||||
g.setFont(font);
|
||||
g.setColor(color);
|
||||
g.drawString(text, point.x, point.y + ascent);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2000-2015 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.impl;
|
||||
|
||||
/**
|
||||
* @author Pavel Fatin
|
||||
*/
|
||||
class SummaryStatistics {
|
||||
private int myCount = 0;
|
||||
|
||||
private double myMin = Double.MAX_VALUE;
|
||||
|
||||
private double myMax = 0.0D;
|
||||
|
||||
private double myMean = 0.0D;
|
||||
|
||||
private double myS = 0.0D;
|
||||
|
||||
void accept(double value) {
|
||||
myCount++;
|
||||
|
||||
myMin = Math.min(myMin, value);
|
||||
|
||||
myMax = Math.max(myMax, value);
|
||||
|
||||
if (myCount == 1) {
|
||||
myMean = value;
|
||||
}
|
||||
else {
|
||||
double previousMean = myMean;
|
||||
myMean += (value - myMean) / myCount;
|
||||
myS += (value - previousMean) * (value - myMean);
|
||||
}
|
||||
}
|
||||
|
||||
double getMin() {
|
||||
return myMin == Double.MAX_VALUE ? 0.0D : myMin;
|
||||
}
|
||||
|
||||
double getMax() {
|
||||
return myMax;
|
||||
}
|
||||
|
||||
double getMean() {
|
||||
return myMean;
|
||||
}
|
||||
|
||||
double getStandardDeviation() {
|
||||
return Math.sqrt(myS / (myCount - 1));
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user