mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-02-04 23:39:07 +07:00
IJPL-149254 Quick Documentation -> Show Table Preview: Columns are incorrectly resized to fit the popup width, condensing the text inside
GitOrigin-RevId: afde85e3970fb17b5fc07718dc7f450552d023ab
This commit is contained in:
committed by
intellij-monorepo-bot
parent
392e797352
commit
66e6d10c4d
@@ -11,33 +11,28 @@ import com.intellij.ui.JBColor;
|
||||
import com.intellij.ui.components.JBHtmlPane;
|
||||
import com.intellij.ui.components.JBHtmlPaneConfiguration;
|
||||
import com.intellij.ui.scale.JBUIScale;
|
||||
import com.intellij.util.SmartList;
|
||||
import com.intellij.util.ui.ExtendableHTMLViewFactory;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.intellij.util.ui.accessibility.ScreenReader;
|
||||
import com.intellij.util.ui.html.UtilsKt;
|
||||
import one.util.streamex.StreamEx;
|
||||
import org.jetbrains.annotations.ApiStatus.Internal;
|
||||
import org.jetbrains.annotations.Nls;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.text.*;
|
||||
import javax.swing.text.BadLocationException;
|
||||
import javax.swing.text.Document;
|
||||
import javax.swing.text.Highlighter;
|
||||
import javax.swing.text.StyledDocument;
|
||||
import javax.swing.text.html.HTML;
|
||||
import javax.swing.text.html.HTMLDocument;
|
||||
import java.awt.*;
|
||||
import java.awt.event.ActionListener;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.Function;
|
||||
|
||||
import static com.intellij.codeInsight.documentation.DocumentationHtmlUtil.*;
|
||||
import static com.intellij.lang.documentation.DocumentationMarkup.CLASS_BOTTOM;
|
||||
import static com.intellij.lang.documentation.DocumentationMarkup.CLASS_DEFINITION;
|
||||
import static com.intellij.lang.documentation.QuickDocHighlightingHelper.getDefaultDocStyleOptions;
|
||||
import static com.intellij.ui.components.impl.JBHtmlPaneStyleSheetRulesProviderKt.CODE_BLOCK_CLASS;
|
||||
|
||||
@Internal
|
||||
public abstract class DocumentationEditorPane extends JBHtmlPane implements Disposable {
|
||||
@@ -91,7 +86,7 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
@NotNull
|
||||
Dimension getPackedSize(int minWidth, int maxWidth) {
|
||||
int width = Math.min(
|
||||
Math.max(Math.max(definitionPreferredWidth(), getMinimumSize().width), minWidth),
|
||||
Math.max(Math.max(contentsPreferredWidth(), getMinimumSize().width), minWidth),
|
||||
maxWidth
|
||||
);
|
||||
int height = getPreferredHeightByWidth(width);
|
||||
@@ -109,56 +104,15 @@ public abstract class DocumentationEditorPane extends JBHtmlPane implements Disp
|
||||
}
|
||||
|
||||
int getPreferredWidth() {
|
||||
int definitionPreferredWidth = definitionPreferredWidth();
|
||||
int definitionPreferredWidth = contentsPreferredWidth();
|
||||
return definitionPreferredWidth < 0 ? getPreferredSize().width
|
||||
: Math.max(definitionPreferredWidth, getMinimumSize().width);
|
||||
}
|
||||
|
||||
private int definitionPreferredWidth() {
|
||||
int preferredDefinitionWidth = getPreferredSectionsWidth(CLASS_DEFINITION);
|
||||
if (preferredDefinitionWidth < 0) {
|
||||
return -1;
|
||||
}
|
||||
int preferredLocationWidth = getPreferredSectionsWidth(CLASS_BOTTOM);
|
||||
int preferredCodeBlockWidth = getPreferredSectionsWidth(CODE_BLOCK_CLASS);
|
||||
private int contentsPreferredWidth() {
|
||||
int elementsPreferredWidth = new DocumentationPanePreferredWidthProvider(getUI().getRootView(this)).get();
|
||||
int preferredContentWidth = getPreferredContentWidth(getDocument().getLength());
|
||||
return Math.max(Math.max(preferredCodeBlockWidth, preferredContentWidth), Math.max(preferredDefinitionWidth, preferredLocationWidth));
|
||||
}
|
||||
|
||||
private int getPreferredSectionsWidth(String sectionClassName) {
|
||||
var definitions = findSections(getUI().getRootView(this), sectionClassName);
|
||||
return StreamEx.of(definitions).mapToInt(it -> getPreferredWidth(it)).max().orElse(-1);
|
||||
}
|
||||
|
||||
private static int getPreferredWidth(View view) {
|
||||
var result = (int)view.getPreferredSpan(View.X_AXIS);
|
||||
if (result > 0) {
|
||||
result += UtilsKt.getWidth(UtilsKt.getCssMargin(view));
|
||||
var parent = view.getParent();
|
||||
while (parent != null) {
|
||||
result += UtilsKt.getWidth(UtilsKt.getCssMargin(parent)) + UtilsKt.getWidth(UtilsKt.getCssPadding(parent));
|
||||
parent = parent.getParent();
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
private static @NotNull List<View> findSections(@NotNull View view, String sectionClassName) {
|
||||
var queue = new ArrayList<View>();
|
||||
queue.add(view);
|
||||
|
||||
var result = new SmartList<View>();
|
||||
while (!queue.isEmpty()) {
|
||||
var cur = queue.remove(queue.size() - 1);
|
||||
if (cur == null) continue;
|
||||
if (sectionClassName.equals(cur.getElement().getAttributes().getAttribute(HTML.Attribute.CLASS))) {
|
||||
result.add(cur);
|
||||
}
|
||||
for (int i = 0; i < cur.getViewCount(); i++) {
|
||||
queue.add(cur.getView(i));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
return Math.max(elementsPreferredWidth, preferredContentWidth);
|
||||
}
|
||||
|
||||
private static int getPreferredContentWidth(int textLength) {
|
||||
|
||||
@@ -0,0 +1,152 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.intellij.codeInsight.documentation
|
||||
|
||||
import com.intellij.lang.documentation.DocumentationMarkup
|
||||
import com.intellij.util.SmartList
|
||||
import com.intellij.util.containers.MultiMap
|
||||
import com.intellij.util.ui.html.cssBorderWidths
|
||||
import com.intellij.util.ui.html.cssMargin
|
||||
import com.intellij.util.ui.html.cssPadding
|
||||
import com.intellij.util.ui.html.width
|
||||
import javax.swing.text.StyleConstants
|
||||
import javax.swing.text.View
|
||||
import javax.swing.text.html.HTML
|
||||
import javax.swing.text.html.InlineView
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
|
||||
internal class DocumentationPanePreferredWidthProvider(
|
||||
private val rootView: View
|
||||
) {
|
||||
|
||||
private val cell2SubCells: MultiMap<View, View> = MultiMap.createSet()
|
||||
private val cellPreferredSectionWidth: HashMap<View, Int> = HashMap()
|
||||
private val viewWidthInsetMap: HashMap<View, Int> = HashMap()
|
||||
|
||||
companion object {
|
||||
private const val MIN_CELL_WIDTH = 75
|
||||
}
|
||||
|
||||
fun get(): Int {
|
||||
val views = findViews(rootView) { view ->
|
||||
val className = view.element.attributes.getAttribute(HTML.Attribute.CLASS) as? String
|
||||
val tagName = view.element.attributes.getAttribute(StyleConstants.NameAttribute) as? HTML.Tag
|
||||
// Let's select all relevant views, i.e. definitions, bottom sections, <pre>, <td> and <code>
|
||||
DocumentationMarkup.CLASS_DEFINITION == className || DocumentationMarkup.CLASS_BOTTOM == className
|
||||
|| tagName === HTML.Tag.PRE || tagName === HTML.Tag.TD
|
||||
|| (view is InlineView && view.cssPadding.width > 0 /* most likely <code> */)
|
||||
}
|
||||
if (views.isEmpty()) return -1
|
||||
|
||||
val preferredWidthNoTables = views.maxOf { view ->
|
||||
var width = view.getPreferredSpan(View.X_AXIS).toInt()
|
||||
if (width < 0) return@maxOf -1
|
||||
if (isTd(view)) {
|
||||
// This is the main difference wrt to regular table layout.
|
||||
// We want table cells to have some minimum size, but not too large;
|
||||
// otherwise, tables would try to occupy as much space as possible to
|
||||
// render each cell in a single line. We want this behavior only with
|
||||
// code blocks or fragments.
|
||||
width = min(width, MIN_CELL_WIDTH)
|
||||
}
|
||||
width -= view.cssPadding.width
|
||||
var cur: View? = view
|
||||
// Let's accumulate ancestors insets width stopping at the root or <td> element
|
||||
while (cur != null) {
|
||||
width += getWidthInsets(cur)
|
||||
// We need to take special care of table cells to calculate table size correctly in case of code fragments and blocks
|
||||
if (isTd(cur)) {
|
||||
cellPreferredSectionWidth.merge(cur, width) { a, b -> Integer.max(a, b) }
|
||||
return@maxOf -1
|
||||
}
|
||||
cur = cur.parent
|
||||
}
|
||||
width
|
||||
}
|
||||
|
||||
if (cellPreferredSectionWidth.isEmpty()) {
|
||||
// It looks like we don't have any tables
|
||||
return preferredWidthNoTables
|
||||
}
|
||||
|
||||
// Let's build a cell dependency tree to be able to iterate over cells in a reasonable manner
|
||||
cellPreferredSectionWidth.keys.forEach {
|
||||
var cell = it
|
||||
var parent = cell.parent
|
||||
while (parent != null) {
|
||||
if (isTd(parent)) {
|
||||
if (cell2SubCells.getModifiable(parent).add(cell)) {
|
||||
cell = parent
|
||||
}
|
||||
else {
|
||||
return@forEach
|
||||
}
|
||||
}
|
||||
parent = parent.parent
|
||||
}
|
||||
cell2SubCells.putValue(null, cell)
|
||||
}
|
||||
|
||||
// Let's start with cells, which do not have any cell ancestors
|
||||
return max(preferredWidthNoTables, getPreferredWidthForRows(null, cell2SubCells[null]))
|
||||
}
|
||||
|
||||
private fun getPreferredWidthForRows(calculationsRoot: View?, cells: Collection<View>?): Int {
|
||||
if (cells.isNullOrEmpty()) return -1
|
||||
// Provided cells should have the same <td> as an ancestor, or no <td> at all
|
||||
|
||||
// Now let's find out all the table rows we need to analyze
|
||||
val rows = cells.asSequence().mapNotNull { it.parent }.toSet()
|
||||
|
||||
// Let's find out the maximum preferred size for any of the rows
|
||||
return rows.maxOf { row ->
|
||||
// Accumulate ancestors insets width till we reach calculations root view
|
||||
var width = 0
|
||||
var cur: View? = row
|
||||
while (cur != null && cur !== calculationsRoot) {
|
||||
width += getWidthInsets(cur)
|
||||
cur = cur.parent
|
||||
}
|
||||
// Return accumulated ancestors width plus sum of cells preferred widths
|
||||
width + IntRange(0, row.viewCount - 1)
|
||||
.map { row.getView(it) }
|
||||
.sumOf { getPreferredWidthForCell(it) }
|
||||
}
|
||||
}
|
||||
|
||||
private fun getPreferredWidthForCell(cell: View): Int {
|
||||
// For a cell, the base preferred width is the maximum width calculated from child code fragments
|
||||
// or an actual preferred span no larger than MIN_CELL_WIDTH
|
||||
val preferredSectionWidth =
|
||||
cellPreferredSectionWidth[cell]
|
||||
?: min(MIN_CELL_WIDTH, cell.getPreferredSpan(View.X_AXIS).toInt().takeIf { it > 0 }?.let { it + cell.cssMargin.width } ?: MIN_CELL_WIDTH)
|
||||
|
||||
// A cell can contain a table, so let's find out the maximum preferred size from any child table
|
||||
return max(preferredSectionWidth, getPreferredWidthForRows(cell.parent, cell2SubCells[cell]))
|
||||
}
|
||||
|
||||
private fun getWidthInsets(view: View) =
|
||||
viewWidthInsetMap.computeIfAbsent(view) { it.cssMargin.width + it.cssPadding.width + it.cssBorderWidths.width }
|
||||
|
||||
private fun isTd(view: View): Boolean {
|
||||
return view.element.attributes.getAttribute(StyleConstants.NameAttribute) == HTML.Tag.TD
|
||||
}
|
||||
|
||||
private fun findViews(view: View, viewSelector: (View) -> Boolean): List<View> {
|
||||
val queue = ArrayList<View>()
|
||||
queue.add(view)
|
||||
|
||||
val result = SmartList<View>()
|
||||
while (!queue.isEmpty()) {
|
||||
val cur = queue.removeAt(queue.size - 1)
|
||||
if (viewSelector(cur)) {
|
||||
result.add(cur)
|
||||
}
|
||||
for (i in 0 until cur.viewCount) {
|
||||
queue.add(cur.getView(i))
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user