From ba5f4f00553e0e89be2bef94f499f1adae76dffc Mon Sep 17 00:00:00 2001 From: Fabio Zadrozny Date: Sun, 3 Sep 2023 09:21:48 -0300 Subject: [PATCH] Don't compute tooltips based on maxWidth (let the viewport clip it). Fixes #PyDev-1233: Eclipse IDE freezes when using a variable with a very long base64 value --- .../AbstractInformationPresenter.java | 17 +-- .../pydev/editor/PyInformationPresenter.java | 106 +++--------------- .../pydev/editor/PyLineBreakReader.java | 97 +--------------- 3 files changed, 22 insertions(+), 198 deletions(-) diff --git a/plugins/org.python.pydev.shared_ui/src/org/python/pydev/shared_ui/tooltips/presenter/AbstractInformationPresenter.java b/plugins/org.python.pydev.shared_ui/src/org/python/pydev/shared_ui/tooltips/presenter/AbstractInformationPresenter.java index 216bcd0dd5..12e333c74e 100644 --- a/plugins/org.python.pydev.shared_ui/src/org/python/pydev/shared_ui/tooltips/presenter/AbstractInformationPresenter.java +++ b/plugins/org.python.pydev.shared_ui/src/org/python/pydev/shared_ui/tooltips/presenter/AbstractInformationPresenter.java @@ -10,7 +10,6 @@ import org.eclipse.jface.text.TextPresentation; import org.eclipse.swt.graphics.Drawable; import org.eclipse.swt.widgets.Display; -import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_core.string.StringUtils; /** @@ -40,21 +39,7 @@ public String updatePresentation(Display display, String hoverInfo, TextPresenta * the ones existing and add the ones dependent on the platform */ protected String correctLineDelimiters(String str) { - FastStringBuffer buf = new FastStringBuffer(); - for (String s : StringUtils.splitInLines(str)) { - - boolean found = false; - while (s.endsWith("\r") || s.endsWith("\n")) { - found = true; - s = s.substring(0, s.length() - 1); - } - buf.append(s); - if (found) { - buf.append(LINE_DELIM); - } - } - str = buf.toString(); - return str; + return StringUtils.replaceNewLines(str, LINE_DELIM); } @Override diff --git a/plugins/org.python.pydev/src/org/python/pydev/editor/PyInformationPresenter.java b/plugins/org.python.pydev/src/org/python/pydev/editor/PyInformationPresenter.java index d86891f86f..0c31aefbec 100644 --- a/plugins/org.python.pydev/src/org/python/pydev/editor/PyInformationPresenter.java +++ b/plugins/org.python.pydev/src/org/python/pydev/editor/PyInformationPresenter.java @@ -13,7 +13,6 @@ import java.util.Arrays; import java.util.Collections; import java.util.Comparator; -import java.util.Iterator; import java.util.List; import org.eclipse.jface.resource.JFaceColors; @@ -34,7 +33,6 @@ import org.eclipse.swt.widgets.Display; import org.eclipse.swt.widgets.TypedListener; import org.python.pydev.ast.item_pointer.ItemPointer; -import org.python.pydev.core.docutils.PyStringUtils; import org.python.pydev.editor.actions.PyOpenAction; import org.python.pydev.shared_core.string.FastStringBuffer; import org.python.pydev.shared_ui.tooltips.presenter.AbstractInformationPresenter; @@ -76,19 +74,8 @@ public boolean similarTo(StyleRange style) { } } - private int fCounter; - private boolean fEnforceUpperLineLimit; private ControlListener resizeCallback; - public PyInformationPresenter(boolean enforceUpperLineLimit) { - super(); - fEnforceUpperLineLimit = enforceUpperLineLimit; - } - - public PyInformationPresenter() { - this(true); - } - private TypedListener resizeListener = new TypedListener(new ControlListener() { @Override @@ -111,7 +98,7 @@ public void controlResized(ControlEvent e) { * Creates the reader and properly puts the presentation into place. */ public Reader createReader(String hoverInfo, TextPresentation presentation) { - String str = PyStringUtils.removeWhitespaceColumnsToLeft(hoverInfo); + String str = hoverInfo; str = correctLineDelimiters(str); @@ -192,63 +179,23 @@ private String handleLinks(List lst, String str, FastStringBuffer return newString; } - protected void adaptTextPresentation(TextPresentation presentation, int offset, int insertLength) { - - int yoursStart = offset; - int yoursEnd = offset + insertLength - 1; - yoursEnd = Math.max(yoursStart, yoursEnd); - - Iterator e = presentation.getAllStyleRangeIterator(); - while (e.hasNext()) { - - StyleRange range = e.next(); - - int myStart = range.start; - int myEnd = range.start + range.length - 1; - myEnd = Math.max(myStart, myEnd); - - if (myEnd < yoursStart) { - continue; - } - - if (myStart < yoursStart) { - range.length += insertLength; - } else { - range.start += insertLength; - } - } - } - - private void append(FastStringBuffer buffer, String string, TextPresentation presentation) { - - int length = string.length(); + private void append(FastStringBuffer buffer, String string) { buffer.append(string); - - if (presentation != null) { - adaptTextPresentation(presentation, fCounter, length); - } - - fCounter += length; - } - - private String getIndent(String line) { - int length = line.length(); - - int i = 0; - while (i < length && Character.isWhitespace(line.charAt(i))) { - ++i; - } - - return (i == length ? line : line.substring(0, i)) + " "; //$NON-NLS-1$ } /* - * @see IHoverInformationPresenterExtension#updatePresentation(Drawable drawable, String, TextPresentation, int, int) + * @see DefaultInformationControl.IHoverInformationPresenterExtension#updatePresentation(Drawable drawable, String, TextPresentation, int, int) * @since 3.2 */ @Override public String updatePresentation(Drawable drawable, String hoverInfo, TextPresentation presentation, int maxWidth, int maxHeight) { + // Note: we don't actually use nor respect maxWidth. + // In practice maxWidth could be used to clip/wrap based on the current viewport, but it's not really helpful + // as the current viewport can clip it already even if we send more information and spending compute + // time for the width can be slow (and when the user actually focuses the tooltip scrolls will be + // properly shown as it'll be requested with a big width/height). + // So, we just clip based on the height as that's faster/easier than calculating based on the width. if (drawable instanceof StyledText) { final StyledText styledText = (StyledText) drawable; styledText.addMouseListener(new MouseAdapter() { @@ -257,7 +204,7 @@ public String updatePresentation(Drawable drawable, String hoverInfo, TextPresen public void mouseDown(MouseEvent e) { int offset; try { - offset = styledText.getOffsetAtLocation(new Point(e.x, e.y)); + offset = styledText.getOffsetAtPoint(new Point(e.x, e.y)); } catch (IllegalArgumentException e1) { return; //invalid location } @@ -296,57 +243,35 @@ public void mouseDown(MouseEvent e) { FastStringBuffer buffer = new FastStringBuffer(); int maxNumberOfLines = Math.round((float) maxHeight / (float) gc.getFontMetrics().getHeight()); - fCounter = 0; - PyLineBreakReader reader = new PyLineBreakReader(createReader(hoverInfo, presentation), gc, maxWidth); - - boolean lastLineFormatted = false; - String lastLineIndent = null; + PyLineBreakReader reader = new PyLineBreakReader(createReader(hoverInfo, presentation)); String line = reader.readLine(); - boolean lineFormatted = reader.isFormattedLine(); boolean firstLineProcessed = false; while (line != null) { - if (fEnforceUpperLineLimit && maxNumberOfLines <= 0) { + if (maxNumberOfLines <= 0) { break; } if (firstLineProcessed) { - if (!lastLineFormatted) { - append(buffer, LINE_DELIM, null); - } else { - append(buffer, LINE_DELIM, presentation); - if (lastLineIndent != null) { - append(buffer, lastLineIndent, presentation); - } - } + append(buffer, LINE_DELIM); } - append(buffer, line, null); + append(buffer, line); firstLineProcessed = true; - lastLineFormatted = lineFormatted; - if (!lineFormatted) { - lastLineIndent = null; - } else if (lastLineIndent == null) { - lastLineIndent = getIndent(line); - } - line = reader.readLine(); - lineFormatted = reader.isFormattedLine(); maxNumberOfLines--; } if (line != null) { - append(buffer, LINE_DELIM, lineFormatted ? presentation : null); + append(buffer, LINE_DELIM); } return trim(buffer, presentation); - } catch (IOException e) { - // ignore TODO do something else? return null; @@ -356,7 +281,6 @@ public void mouseDown(MouseEvent e) { } private String trim(FastStringBuffer buffer, TextPresentation presentation) { - int length = buffer.length(); int end = length - 1; diff --git a/plugins/org.python.pydev/src/org/python/pydev/editor/PyLineBreakReader.java b/plugins/org.python.pydev/src/org/python/pydev/editor/PyLineBreakReader.java index 664bb1ef9c..cebb9bb17e 100644 --- a/plugins/org.python.pydev/src/org/python/pydev/editor/PyLineBreakReader.java +++ b/plugins/org.python.pydev/src/org/python/pydev/editor/PyLineBreakReader.java @@ -9,115 +9,30 @@ import java.io.BufferedReader; import java.io.IOException; import java.io.Reader; -import java.text.BreakIterator; - -import org.eclipse.swt.graphics.GC; public class PyLineBreakReader { private BufferedReader fReader; - private GC fGC; - private int fMaxWidth; - - private String fLine; - private int fOffset; - - private BreakIterator fLineBreakIterator; - private boolean fBreakWords; /** * Creates a reader that breaks an input text to fit in a given width. - * + * * @param reader Reader of the input text - * @param gc The graphic context that defines the currently used font sizes * @param maxLineWidth The max width (pixels) where the text has to fit in */ - public PyLineBreakReader(Reader reader, GC gc, int maxLineWidth) { + public PyLineBreakReader(Reader reader) { fReader = new BufferedReader(reader); - fGC = gc; - fMaxWidth = maxLineWidth; - fOffset = 0; - fLine = null; - fLineBreakIterator = BreakIterator.getLineInstance(); - fBreakWords = true; - } - - public boolean isFormattedLine() { - return fLine != null; } /** * Reads the next line. The lengths of the line will not exceed the given maximum * width. - * - * @return the next line - * @throws IOException + * + * @return the next line + * @throws IOException */ public String readLine() throws IOException { - if (fLine == null) { - String line = fReader.readLine(); - if (line == null) - return null; - - int lineLen = fGC.textExtent(line).x; - if (lineLen < fMaxWidth) { - return line; - } - fLine = line; - fLineBreakIterator.setText(line); - fOffset = 0; - } - int breakOffset = findNextBreakOffset(fOffset); - String res; - if (breakOffset != BreakIterator.DONE) { - res = fLine.substring(fOffset, breakOffset); - fOffset = findWordBegin(breakOffset); - if (fOffset == fLine.length()) { - fLine = null; - } - } else { - res = fLine.substring(fOffset); - fLine = null; - } - return res; + return fReader.readLine(); } - private int findNextBreakOffset(int currOffset) { - int currWidth = 0; - int nextOffset = fLineBreakIterator.following(currOffset); - while (nextOffset != BreakIterator.DONE) { - String word = fLine.substring(currOffset, nextOffset); - int wordWidth = fGC.textExtent(word).x; - int nextWidth = wordWidth + currWidth; - if (nextWidth > fMaxWidth) { - if (currWidth > 0) - return currOffset; - - if (!fBreakWords) - return nextOffset; - - // need to fit into fMaxWidth - int length = word.length(); - while (length >= 0) { - length--; - word = word.substring(0, length); - wordWidth = fGC.textExtent(word).x; - if (wordWidth + currWidth < fMaxWidth) - return currOffset + length; - } - return nextOffset; - } - currWidth = nextWidth; - currOffset = nextOffset; - nextOffset = fLineBreakIterator.next(); - } - return nextOffset; - } - - private int findWordBegin(int idx) { - while (idx < fLine.length() && Character.isWhitespace(fLine.charAt(idx))) { - idx++; - } - return idx; - } }