mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-19 13:02:30 +07:00
[PyCharm Data Viewer] Refactoring and PY-44817. Extract the common part from the old and new tables. Added an ability to save format for all tables #PY-44817 Fixed
* Added checkbox to allow remembering an inserted format for all tables * Added an ability to save format between ide sessions * Extracted the common part of tables in a separate class. Merge-request: IJ-MR-158219 Merged-by: Natalia Murycheva <natalia.murycheva@jetbrains.com> GitOrigin-RevId: a33ea8ed7174e45b151ade0cd36717ab61d97427
This commit is contained in:
committed by
intellij-monorepo-bot
parent
dff61fdd49
commit
3757fcb978
@@ -823,14 +823,14 @@ debugger.test.failed.caption=Test failed
|
||||
debugger.error.in.test.setup.or.teardown.caption=Error in test set up or tear down
|
||||
debugger.remote.port.out.of.boundaries=The port number is out of boundaries
|
||||
|
||||
debugger.dataviewer.action.export.name=Export\u2026
|
||||
debugger.dataviewer.action.export.dialog.description=Save to
|
||||
debugger.dataviewer.action.export.dialog.label=Export files
|
||||
debugger.dataviewer.export.error.title=Table export failed
|
||||
debugger.dataviewer.export.error.invalid.filepath=Invalid filepath
|
||||
debugger.dataviewer.export.error.invalid.filepath.content=Filepath ''{0}'' is invalid
|
||||
debugger.dataviewer.export.error.unhandled=Unhandled error
|
||||
debugger.dataviewer.export.error.unhandled.content=Couldn''t export to ''{0}'':\n{1}
|
||||
debugger.dataViewer.action.export.name=Export\u2026
|
||||
debugger.dataViewer.action.export.dialog.description=Save to
|
||||
debugger.dataViewer.action.export.dialog.label=Export files
|
||||
debugger.dataViewer.export.error.title=Table export failed
|
||||
debugger.dataViewer.export.error.invalid.filepath=Invalid filepath
|
||||
debugger.dataViewer.export.error.invalid.filepath.content=Filepath ''{0}'' is invalid
|
||||
debugger.dataViewer.export.error.unhandled=Unhandled error
|
||||
debugger.dataViewer.export.error.unhandled.content=Couldn''t export to ''{0}'':\n{1}
|
||||
debugger.dataviewer.notification.group.title=Data Viewer error
|
||||
debugger.dataviewer.action.copy.name=Copy
|
||||
debugger.dataviewer.action.set.filter.name=Set Filter\u2026
|
||||
@@ -850,11 +850,14 @@ debugger.dataviewer.action.copy.properties.include.indices=Include indices
|
||||
debugger.dataviewer.action.copy.properties.separator=Separator:
|
||||
debugger.dataviewer.action.copy.update.message=Copy to clipboard
|
||||
debugger.dataviewer.action.remove.filter.name=Remove Filter
|
||||
debugger.dataviewer.modifier.error=Modifier error: {0}
|
||||
debugger.dataViewer.modifier.error=Modifier error: {0}
|
||||
debugger.dataviewer.header.filter.hint={0}: {1}
|
||||
debugger.dataviewer.header.filter.hint.mode.expression=Expression
|
||||
debugger.dataviewer.header.filter.hint.mode.regex=RegEx
|
||||
debugger.dataviewer.header.filter.hint.mode.substring=Substring
|
||||
debugger.dataViewer.dataframes.unsupported=<html>The old view does not support this dataframe type.</html>
|
||||
debugger.dataViewer.switch.between.tables.gotIt.text=Here you can switch between new and old table modes for viewing data.
|
||||
debugger.dataViewer.switch.between.tables.gotIt.link=Switch mode
|
||||
|
||||
debugger.remote.waiting.for.process.connection=Waiting for process connection\u2026
|
||||
debugger.remote.waiting.for.connection=Waiting for connection
|
||||
|
||||
@@ -6,7 +6,7 @@ import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.ColumnFilter;
|
||||
import com.jetbrains.python.debugger.containerview.DataViewStrategy;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerPanel;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerCommunityPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
@@ -46,7 +46,7 @@ public class ArrayViewStrategy extends DataViewStrategy {
|
||||
@Override
|
||||
public AsyncArrayTableModel createTableModel(int rowCount,
|
||||
int columnCount,
|
||||
@NotNull PyDataViewerPanel panel,
|
||||
@NotNull PyDataViewerCommunityPanel panel,
|
||||
@NotNull PyDebugValue debugValue) {
|
||||
return new AsyncArrayTableModel(rowCount, columnCount, panel, debugValue, this);
|
||||
}
|
||||
|
||||
@@ -21,7 +21,7 @@ import com.jetbrains.python.debugger.ArrayChunkBuilder;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.PyDebuggerException;
|
||||
import com.jetbrains.python.debugger.containerview.DataViewStrategy;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerPanel;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerCommunityPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.event.TableModelEvent;
|
||||
@@ -37,7 +37,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
|
||||
private final int myRows;
|
||||
private final int myColumns;
|
||||
private final PyDataViewerPanel myDataProvider;
|
||||
private final PyDataViewerCommunityPanel myDataProvider;
|
||||
|
||||
|
||||
private final ExecutorService myExecutorService = ConcurrencyUtil.newSingleThreadExecutor("Python async table");
|
||||
@@ -53,7 +53,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
return ListenableFutureTask.create(() -> {
|
||||
ArrayChunk chunk = myDebugValue.getFrameAccessor()
|
||||
.getArrayItems(myDebugValue, key.first, key.second, Math.min(CHUNK_ROW_SIZE, getRowCount() - key.first),
|
||||
Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second), myDataProvider.getFormat());
|
||||
Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second), myDataProvider.getDataViewerModel().getFormat());
|
||||
handleChunkAdded(key.first, key.second, chunk);
|
||||
return chunk;
|
||||
});
|
||||
@@ -62,7 +62,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
|
||||
public AsyncArrayTableModel(int rows,
|
||||
int columns,
|
||||
PyDataViewerPanel provider,
|
||||
PyDataViewerCommunityPanel provider,
|
||||
PyDebugValue debugValue,
|
||||
DataViewStrategy strategy) {
|
||||
myRows = rows;
|
||||
@@ -131,7 +131,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
try {
|
||||
ArrayChunk chunk = myDebugValue.getFrameAccessor()
|
||||
.getArrayItems(myDebugValue, fromRow, fromCol, toRow - fromRow + 1, toCol - fromCol + 1,
|
||||
myDataProvider.getFormat());
|
||||
myDataProvider.getDataViewerModel().getFormat());
|
||||
|
||||
if (chunk != null) {
|
||||
whenLoaded.accept(chunk);
|
||||
|
||||
@@ -34,7 +34,7 @@ public abstract class DataViewStrategy {
|
||||
|
||||
public abstract AsyncArrayTableModel createTableModel(int rowCount,
|
||||
int columnCount,
|
||||
@NotNull PyDataViewerPanel panel,
|
||||
@NotNull PyDataViewerCommunityPanel panel,
|
||||
@NotNull PyDebugValue debugValue);
|
||||
|
||||
public abstract ColoredCellRenderer createCellRenderer(double minValue, double maxValue, @NotNull ArrayChunk arrayChunk);
|
||||
|
||||
@@ -91,7 +91,7 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
window.contentManager.getReady(this).doWhenDone {
|
||||
val selectedInfo = addTab(value.frameAccessor)
|
||||
val dataViewerPanel = selectedInfo.component as PyDataViewerPanel
|
||||
dataViewerPanel.apply(value, false)
|
||||
dataViewerPanel.component.apply(value, false)
|
||||
window.show {
|
||||
window.component.requestFocusInWindow()
|
||||
dataViewerPanel.requestFocusInWindow()
|
||||
@@ -103,7 +103,7 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
val tabsToRemove: MutableList<Content> = ArrayList()
|
||||
|
||||
contentManager.contents.forEach {
|
||||
if (ifClose.test(getPanel(it.component).frameAccessor)) {
|
||||
if (ifClose.test(getPanel(it.component).dataViewerModel.frameAccessor)) {
|
||||
tabsToRemove.add(it)
|
||||
}
|
||||
}
|
||||
@@ -111,7 +111,6 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
tabsToRemove.forEach {
|
||||
contentManager.removeContent(it, true)
|
||||
getPanel(it.component).closeEditorTabs()
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -119,8 +118,8 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
fun updateTabs(handler: ProcessHandler) {
|
||||
saveSelectedInfo()
|
||||
contentManager.contents.forEach { content ->
|
||||
val panel: PyDataViewerPanel = getPanel(content.component)
|
||||
val accessor = panel.frameAccessor
|
||||
val panel: PyDataViewerCommunityPanel = getPanel(content.component)
|
||||
val accessor = panel.dataViewerModel.frameAccessor
|
||||
if (accessor !is PyDebugProcess) {
|
||||
return@forEach
|
||||
}
|
||||
@@ -145,7 +144,7 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
private fun saveSelectedInfo() {
|
||||
val selectedInfo = contentManager.selectedContent
|
||||
if (!hasOnlyEmptyTab() && selectedInfo != null) {
|
||||
val accessor: PyFrameAccessor = getPanel(selectedInfo.component).frameAccessor
|
||||
val accessor: PyFrameAccessor = getPanel(selectedInfo.component).dataViewerModel.frameAccessor
|
||||
if (accessor is PyDebugProcess) {
|
||||
selectedInfos[accessor.processHandler] = selectedInfo
|
||||
}
|
||||
@@ -173,14 +172,7 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
contentManager.removeAllContents(true)
|
||||
}
|
||||
|
||||
var panel: PyDataViewerPanel? = null
|
||||
for (factory in PyDataViewPanelFactory.EP_NAME.extensionList) {
|
||||
panel = factory.createDataViewPanel(project, frameAccessor)
|
||||
if (panel != null) break
|
||||
}
|
||||
if (panel == null) {
|
||||
panel = PyDataViewerPanel(project, frameAccessor)
|
||||
}
|
||||
val panel = PyDataViewerPanel(project, frameAccessor)
|
||||
|
||||
val content = ContentFactory.getInstance().createContent(panel, null, false)
|
||||
content.isCloseable = true
|
||||
@@ -198,7 +190,7 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
if (window is ToolWindowEx) {
|
||||
window.setTabActions(NewViewerAction(frameAccessor))
|
||||
}
|
||||
panel.addListener(PyDataViewerPanel.Listener {
|
||||
panel.addListener( PyDataViewerAbstractPanel.OnNameChangedListener {
|
||||
content.displayName = it
|
||||
})
|
||||
Disposer.register(content, panel)
|
||||
@@ -233,15 +225,15 @@ class PyDataView(private val project: Project) : DumbAware {
|
||||
}
|
||||
}
|
||||
|
||||
fun getPanel(component: JComponent): PyDataViewerPanel {
|
||||
return component as PyDataViewerPanel
|
||||
fun getPanel(component: JComponent): PyDataViewerCommunityPanel {
|
||||
return component as PyDataViewerCommunityPanel
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val DATA_VIEWER_ID = "SciView"
|
||||
|
||||
const val COLORED_BY_DEFAULT = "python.debugger.dataview.coloredbydefault"
|
||||
const val AUTO_RESIZE = "python.debugger.dataview.autoresize"
|
||||
const val COLORED_BY_DEFAULT: String = "python.debugger.dataView.coloredByDefault"
|
||||
const val AUTO_RESIZE: String = "python.debugger.dataView.autoresize"
|
||||
private const val HELP_ID = "reference.toolWindows.PyDataView"
|
||||
|
||||
@JvmStatic
|
||||
|
||||
@@ -10,6 +10,7 @@ import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.ui.DialogWrapper
|
||||
import com.intellij.openapi.updateSettings.impl.pluginsAdvertisement.installAndEnable
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.EditorNotificationPanel
|
||||
import com.intellij.ui.components.JBCheckBox
|
||||
import com.intellij.util.PlatformUtils
|
||||
@@ -21,15 +22,13 @@ import com.jetbrains.python.debugger.containerview.PyDataView.Companion.isColori
|
||||
import com.jetbrains.python.debugger.containerview.PyDataView.Companion.setAutoResizeEnabled
|
||||
import com.jetbrains.python.debugger.containerview.PyDataView.Companion.setColoringEnabled
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.ActionEvent
|
||||
import java.awt.event.ActionListener
|
||||
import javax.swing.Action
|
||||
import javax.swing.BoxLayout
|
||||
import javax.swing.JPanel
|
||||
|
||||
class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : DialogWrapper(myProject, false) {
|
||||
private val mainPanel: JPanel
|
||||
private val dataViewerPanel: PyDataViewerPanel
|
||||
private val dataViewerPanel: PyDataViewerCommunityPanel
|
||||
private var jupyterSuggestionPanel: JPanel? = null
|
||||
|
||||
init {
|
||||
@@ -37,7 +36,7 @@ class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : Di
|
||||
setCancelButtonText(PyBundle.message("debugger.data.view.close"))
|
||||
setCrossClosesWindow(true)
|
||||
|
||||
dataViewerPanel = PyDataViewerPanel(myProject, value.frameAccessor)
|
||||
dataViewerPanel = PyDataViewerCommunityPanel(PyDataViewerModel(myProject, value.frameAccessor))
|
||||
dataViewerPanel.apply(value, modifier = false, commandSource = null)
|
||||
dataViewerPanel.preferredSize = JBUI.size(TABLE_DEFAULT_WIDTH, TABLE_DEFAULT_HEIGHT)
|
||||
|
||||
@@ -52,8 +51,8 @@ class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : Di
|
||||
|
||||
mainPanel.add(dataViewerPanel, BorderLayout.CENTER)
|
||||
|
||||
dataViewerPanel.addListener(object : PyDataViewerPanel.Listener {
|
||||
override fun onNameChanged(name: @NlsContexts.TabTitle String) {
|
||||
dataViewerPanel.addListener(object : PyDataViewerAbstractPanel.OnNameChangedListener {
|
||||
override fun onNameChanged(@NlsSafe name: @NlsContexts.TabTitle String) {
|
||||
title = name
|
||||
}
|
||||
})
|
||||
@@ -64,27 +63,23 @@ class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : Di
|
||||
init()
|
||||
}
|
||||
|
||||
override fun getDimensionServiceKey() = "#com.jetbrains.python.debugger.containerview.PyDataViewDialog"
|
||||
override fun getDimensionServiceKey(): String = "#com.jetbrains.python.debugger.containerview.PyDataViewDialog"
|
||||
|
||||
private fun createBottomElements(): JPanel {
|
||||
val colored = JBCheckBox(PyBundle.message("debugger.data.view.colored.cells"))
|
||||
colored.setSelected(isColoringEnabled(myProject))
|
||||
colored.addActionListener(object : ActionListener {
|
||||
override fun actionPerformed(e: ActionEvent?) {
|
||||
setColoringEnabled(myProject, colored.isSelected)
|
||||
dataViewerPanel.isColored = colored.isSelected
|
||||
}
|
||||
})
|
||||
colored.addActionListener {
|
||||
setColoringEnabled(myProject, colored.isSelected)
|
||||
dataViewerPanel.dataViewerModel.isColored = colored.isSelected
|
||||
}
|
||||
|
||||
val resize = JBCheckBox(PyBundle.message("debugger.data.view.resize.automatically"))
|
||||
resize.setSelected(isAutoResizeEnabled(myProject))
|
||||
resize.addActionListener(object : ActionListener {
|
||||
override fun actionPerformed(e: ActionEvent?) {
|
||||
setAutoResizeEnabled(myProject, resize.isSelected)
|
||||
dataViewerPanel.resize(resize.isSelected)
|
||||
dataViewerPanel.updateUI()
|
||||
}
|
||||
})
|
||||
resize.addActionListener {
|
||||
setAutoResizeEnabled(myProject, resize.isSelected)
|
||||
dataViewerPanel.resize(resize.isSelected)
|
||||
dataViewerPanel.updateUI()
|
||||
}
|
||||
|
||||
return JPanel().apply {
|
||||
layout = BoxLayout(this, BoxLayout.Y_AXIS)
|
||||
@@ -93,9 +88,9 @@ class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : Di
|
||||
}
|
||||
}
|
||||
|
||||
override fun createActions() = arrayOf<Action>(cancelAction)
|
||||
override fun createActions(): Array<Action> = arrayOf(cancelAction)
|
||||
|
||||
override fun createCenterPanel() = mainPanel
|
||||
override fun createCenterPanel(): JPanel = mainPanel
|
||||
|
||||
private fun createJupyterSuggestionPanel(): JPanel? {
|
||||
if (PlatformUtils.isCommunityEdition()) return null
|
||||
@@ -166,7 +161,7 @@ class PyDataViewDialog(private val myProject: Project, value: PyDebugValue) : Di
|
||||
private const val TABLE_DEFAULT_WIDTH = 700
|
||||
private const val TABLE_DEFAULT_HEIGHT = 500
|
||||
|
||||
private const val JUPYTER_SUGGESTION_ENABLED_PROPERTY_KEY = "python.debugger.dataview.jupyter.suggestion.enabled"
|
||||
private const val JUPYTER_SUGGESTION_ENABLED_PROPERTY_KEY = "python.debugger.dataView.jupyter.suggestion.enabled"
|
||||
|
||||
private fun isJupyterSuggestionEnabled(project: Project) = PropertiesComponent.getInstance(project).getBoolean(JUPYTER_SUGGESTION_ENABLED_PROPERTY_KEY, true)
|
||||
|
||||
|
||||
@@ -2,11 +2,9 @@
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.extensions.ExtensionPointName;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.jetbrains.python.debugger.PyFrameAccessor;
|
||||
|
||||
public abstract class PyDataViewPanelFactory {
|
||||
public static final ExtensionPointName<PyDataViewPanelFactory> EP_NAME = ExtensionPointName.create("Pythonid.dataViewPanelFactory");
|
||||
|
||||
public abstract PyDataViewerPanel createDataViewPanel(Project project, PyFrameAccessor frameAccessor);
|
||||
public abstract PyDataViewerAbstractPanel createDataViewerPanel(PyDataViewerModel state);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,211 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.debugger.containerview
|
||||
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.debugger.ArrayChunk
|
||||
import com.jetbrains.python.debugger.PyDebugValue
|
||||
import com.jetbrains.python.debugger.PyDebuggerException
|
||||
import com.jetbrains.python.debugger.PyFrameAccessor
|
||||
import com.jetbrains.python.debugger.statistics.PyDataViewerCollector
|
||||
import org.jetbrains.annotations.Nls
|
||||
import java.awt.BorderLayout
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
abstract class PyDataViewerAbstractPanel(
|
||||
val dataViewerModel: PyDataViewerModel,
|
||||
val isPanelFromFactory: Boolean = false,
|
||||
) : JPanel(BorderLayout()), Disposable {
|
||||
|
||||
/**
|
||||
* Represents a formatting string used for specifying format.
|
||||
*/
|
||||
abstract var formatValueFromUI: String
|
||||
|
||||
/**
|
||||
* Represents a slicing string used for slicing data in a viewer panel.
|
||||
* For example, np_array[0] or df['column_1'].
|
||||
*/
|
||||
protected abstract var slicingValueFromUI: String
|
||||
|
||||
abstract var isColoredValueFromUI: Boolean
|
||||
|
||||
abstract val slicingTextField: EditorTextField
|
||||
|
||||
abstract var topToolbar: JPanel?
|
||||
|
||||
protected val listeners: CopyOnWriteArrayList<OnNameChangedListener> = CopyOnWriteArrayList<OnNameChangedListener>()
|
||||
|
||||
protected abstract fun setError(text: @NlsContexts.Label String, modifier: Boolean)
|
||||
|
||||
protected abstract fun setupDataProvider()
|
||||
|
||||
protected abstract fun updateUI(chunk: ArrayChunk, originalDebugValue: PyDebugValue, strategy: DataViewStrategy, modifier: Boolean)
|
||||
|
||||
abstract fun createTable(originalDebugValue: PyDebugValue? = null, chunk: ArrayChunk? = null): JComponent
|
||||
|
||||
abstract fun recreateTable()
|
||||
|
||||
protected fun onEnterPressed(commandSource: TextFieldCommandSource) {
|
||||
apply(commandSource)
|
||||
}
|
||||
|
||||
fun apply(commandSource: TextFieldCommandSource) {
|
||||
dataViewerModel.format = formatValueFromUI
|
||||
dataViewerModel.slicing = slicingValueFromUI
|
||||
dataViewerModel.isColored = isColoredValueFromUI
|
||||
|
||||
apply(dataViewerModel.slicing, false, commandSource)
|
||||
}
|
||||
|
||||
fun apply(name: String?, modifier: Boolean, commandSource: TextFieldCommandSource? = null) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
val debugValue = getDebugValue(name, modifier)
|
||||
ApplicationManager.getApplication().invokeLater { debugValue?.let { apply(it, modifier, commandSource) } }
|
||||
}
|
||||
}
|
||||
|
||||
fun apply(debugValue: PyDebugValue, modifier: Boolean, commandSource: TextFieldCommandSource? = null) {
|
||||
if (!modifier) {
|
||||
when (commandSource) {
|
||||
TextFieldCommandSource.SLICING -> PyDataViewerCollector.logDataSlicingApplied(isPanelFromFactory)
|
||||
TextFieldCommandSource.FORMATTING -> PyDataViewerCollector.logDataFormattingApplied(isPanelFromFactory)
|
||||
else -> Unit
|
||||
}
|
||||
|
||||
val dimensions = getValueDimensions(debugValue)
|
||||
PyDataViewerCollector.logDataOpened(dataViewerModel.project, debugValue.type,
|
||||
dimensions?.size,
|
||||
dimensions?.getOrNull(0) ?: 0,
|
||||
dimensions?.getOrNull(1) ?: 0,
|
||||
isNewTable = isPanelFromFactory)
|
||||
}
|
||||
|
||||
val type = debugValue.type
|
||||
val strategy = DataViewStrategy.getStrategy(type)
|
||||
if (strategy == null) {
|
||||
setError(PyBundle.message("debugger.data.view.type.is.not.supported", type), modifier)
|
||||
return
|
||||
}
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
try {
|
||||
doStrategyInitExecution(debugValue.frameAccessor, strategy)
|
||||
val arrayChunk = debugValue.frameAccessor.getArrayItems(debugValue, 0, 0, 0, 0, formatValueFromUI)
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
updateUI(arrayChunk, debugValue, strategy, modifier)
|
||||
dataViewerModel.isModified = modifier
|
||||
dataViewerModel.debugValue = debugValue
|
||||
}
|
||||
}
|
||||
catch (e: IllegalArgumentException) {
|
||||
ApplicationManager.getApplication().invokeLater { setError(e.localizedMessage, modifier) } //NON-NLS
|
||||
}
|
||||
catch (e: PyDebuggerException) {
|
||||
thisLogger().error(e)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
if (e.message?.contains("Numpy is not available") == true) {
|
||||
setError(PyBundle.message("debugger.data.view.numpy.is.not.available", type), modifier)
|
||||
}
|
||||
thisLogger().error("PyDataViewer.apply: Numpy is not available", e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* PyDebugValue shape in case of arrays could be like (10, 14, 23),
|
||||
* and we can extract these dimensions for analysis and logging.
|
||||
*/
|
||||
private fun getValueDimensions(debugValue: PyDebugValue): List<Int>? {
|
||||
val shape = debugValue.shape?.takeIf { it.startsWith("(") && it.endsWith(")") } ?: return null
|
||||
return shape
|
||||
.removeSurrounding("(", ")")
|
||||
.split(",")
|
||||
.filter { it.isNotEmpty() }
|
||||
.mapNotNull { it.trim().toIntOrNull() }
|
||||
.takeIf { it.size == shape.count { ch -> ch == ',' } + 1 }
|
||||
}
|
||||
|
||||
@Throws(PyDebuggerException::class)
|
||||
protected fun doStrategyInitExecution(frameAccessor: PyFrameAccessor, strategy: DataViewStrategy) {
|
||||
val execString = strategy.initExecuteString ?: return
|
||||
frameAccessor.evaluate(execString, true, false)
|
||||
}
|
||||
|
||||
protected fun updateTabNameSlicingFieldAndFormatField(chunk: ArrayChunk?, originalDebugValue: PyDebugValue, modifier: Boolean) {
|
||||
// Debugger generates a temporary name for every slice evaluation, so we should select a correct name for it
|
||||
val debugValue = chunk?.value
|
||||
val realName = if (debugValue == null || debugValue.name == originalDebugValue.tempName) originalDebugValue.name else chunk.slicePresentation
|
||||
var shownName = realName
|
||||
if (modifier && dataViewerModel.originalVarName != shownName) {
|
||||
shownName = String.format(MODIFIED_VARIABLE_FORMAT, dataViewerModel.originalVarName)
|
||||
}
|
||||
else {
|
||||
dataViewerModel.originalVarName = realName
|
||||
}
|
||||
dataViewerModel.originalVarName?.let { slicingValueFromUI = it }
|
||||
|
||||
// Modifier flag means that variable changes are temporary
|
||||
dataViewerModel.modifiedVarName = realName
|
||||
|
||||
for (listener in listeners) {
|
||||
listener.onNameChanged(shownName)
|
||||
}
|
||||
|
||||
if (chunk != null) {
|
||||
formatValueFromUI = chunk.format
|
||||
}
|
||||
}
|
||||
|
||||
protected fun getDebugValue(expression: @NlsSafe String?, modifier: Boolean): PyDebugValue? {
|
||||
return try {
|
||||
val debugValue = dataViewerModel.frameAccessor.evaluate(expression, false, true)
|
||||
if (debugValue == null || debugValue.isErrorOnEval) {
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
val debugValueExpression = debugValue.value
|
||||
val errorText = if (debugValue != null && debugValueExpression != null) {
|
||||
debugValueExpression
|
||||
} else {
|
||||
PyBundle.message("debugger.data.view.failed.to.evaluate.expression", expression)
|
||||
}
|
||||
|
||||
setError(errorText, modifier)
|
||||
}
|
||||
null
|
||||
}
|
||||
else {
|
||||
debugValue
|
||||
}
|
||||
}
|
||||
catch (e: PyDebuggerException) {
|
||||
ApplicationManager.getApplication().invokeLater { setError(e.getTracebackError(), modifier) } //NON-NLS
|
||||
null
|
||||
}
|
||||
}
|
||||
|
||||
fun addListener(onNameChangedListener: OnNameChangedListener) {
|
||||
listeners.add(onNameChangedListener)
|
||||
}
|
||||
|
||||
protected fun composeErrorMessage(text: @NlsSafe String, modifier: Boolean): @Nls String {
|
||||
return if (modifier) PyBundle.message("debugger.dataViewer.modifier.error", text) else text
|
||||
}
|
||||
|
||||
override fun dispose(): Unit = Unit
|
||||
|
||||
fun interface OnNameChangedListener {
|
||||
fun onNameChanged(name: @NlsContexts.TabTitle String)
|
||||
}
|
||||
|
||||
companion object {
|
||||
private const val MODIFIED_VARIABLE_FORMAT = "%s*"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,273 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.debugger.containerview
|
||||
|
||||
import com.intellij.openapi.actionSystem.ActionManager
|
||||
import com.intellij.openapi.actionSystem.DataKey
|
||||
import com.intellij.openapi.actionSystem.DataProvider
|
||||
import com.intellij.openapi.actionSystem.DefaultActionGroup
|
||||
import com.intellij.openapi.actionSystem.toolbarLayout.ToolbarLayoutStrategy
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.components.TwoSideComponent
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.ui.dsl.builder.text
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonFileType
|
||||
import com.jetbrains.python.debugger.ArrayChunk
|
||||
import com.jetbrains.python.debugger.PyDebugValue
|
||||
import com.jetbrains.python.debugger.PyFrameListener
|
||||
import com.jetbrains.python.debugger.array.AbstractDataViewTable
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel
|
||||
import com.jetbrains.python.debugger.array.JBTableWithRowHeaders
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.JEditorPane
|
||||
import javax.swing.JPanel
|
||||
|
||||
class PyDataViewerCommunityPanel(
|
||||
dataViewerModel: PyDataViewerModel,
|
||||
) : PyDataViewerAbstractPanel(dataViewerModel, false) {
|
||||
|
||||
private var table: AbstractDataViewTable? = null
|
||||
|
||||
private val panelWithTable: JPanel = JPanel(BorderLayout())
|
||||
|
||||
private var formatTextField: EditorTextField = createEditorField(TextFieldCommandSource.FORMATTING)
|
||||
|
||||
override val slicingTextField: EditorTextField = createEditorField(TextFieldCommandSource.SLICING)
|
||||
|
||||
override var topToolbar: JPanel? = null
|
||||
|
||||
override var formatValueFromUI: String = ""
|
||||
get() {
|
||||
val format = formatTextField.text
|
||||
return format.ifEmpty { "%s" }
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
formatTextField.text = value
|
||||
}
|
||||
|
||||
override var slicingValueFromUI: String = ""
|
||||
get() {
|
||||
val slicing = slicingTextField.text
|
||||
return slicing.ifEmpty { "None" }
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
slicingTextField.text = value
|
||||
}
|
||||
|
||||
override var isColoredValueFromUI: Boolean
|
||||
get() = dataViewerModel.protectedColored
|
||||
set(value) {
|
||||
dataViewerModel.protectedColored = value
|
||||
val table = table
|
||||
if (table != null && !table.isEmpty) {
|
||||
(table.getDefaultRenderer(table.getColumnClass(0)) as ColoredCellRenderer).setColored(value)
|
||||
table.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
private val model: AsyncArrayTableModel?
|
||||
get() {
|
||||
return table?.model as? AsyncArrayTableModel
|
||||
}
|
||||
|
||||
private lateinit var errorLabel: Cell<JEditorPane>
|
||||
|
||||
init {
|
||||
dataViewerModel.PyDataViewerCompletionProvider().apply(slicingTextField)
|
||||
formatTextField.text = dataViewerModel.format
|
||||
|
||||
add(panelWithTable, BorderLayout.CENTER)
|
||||
add(panel {
|
||||
row {
|
||||
cell(slicingTextField).align(AlignX.FILL).resizableColumn()
|
||||
label(PyBundle.message("form.data.viewer.format"))
|
||||
cell(formatTextField)
|
||||
}
|
||||
row {
|
||||
errorLabel = text("").apply { component.setForeground(JBColor.RED) }
|
||||
}
|
||||
}, BorderLayout.SOUTH)
|
||||
|
||||
setupChangeListener()
|
||||
|
||||
topToolbar = createAndSetupTopToolbar()
|
||||
}
|
||||
|
||||
override fun recreateTable() {
|
||||
panelWithTable.removeAll()
|
||||
panelWithTable.add(createTable().scrollPane, BorderLayout.CENTER)
|
||||
}
|
||||
|
||||
override fun setupDataProvider() {
|
||||
val toolbarDataProvider = DataProvider { dataId ->
|
||||
if (PY_DATA_VIEWER_COMMUNITY_PANEL_KEY.`is`(dataId)) table else null
|
||||
}
|
||||
|
||||
PyDataViewerPanel.addDataProvider(this, toolbarDataProvider)
|
||||
}
|
||||
|
||||
private fun createAndSetupTopToolbar(): JPanel {
|
||||
val actionManager = ActionManager.getInstance()
|
||||
val leftActionGroup = DefaultActionGroup(actionManager.getAction("ToggleDataViewColoring"))
|
||||
val leftActionToolbar = actionManager.createActionToolbar("PyDataView", leftActionGroup, true)
|
||||
leftActionToolbar.setTargetComponent(panelWithTable)
|
||||
|
||||
val rightActionGroup = DefaultActionGroup()
|
||||
rightActionGroup.add(actionManager.getAction("OpenInEditorAction"))
|
||||
rightActionGroup.add(actionManager.getAction("ExportTableAction"))
|
||||
rightActionGroup.add(actionManager.getAction("SwitchBetweenTableModesAction"))
|
||||
|
||||
val rightActionToolbar = actionManager.createActionToolbar("PyDataView", rightActionGroup, true).apply {
|
||||
layoutStrategy = ToolbarLayoutStrategy.NOWRAP_STRATEGY // For removing the empty space on the right of the toolbar.
|
||||
}
|
||||
rightActionToolbar.setTargetComponent(panelWithTable)
|
||||
|
||||
val twoSideComponent = TwoSideComponent(leftActionToolbar.component, rightActionToolbar.component)
|
||||
add(twoSideComponent, BorderLayout.BEFORE_FIRST_LINE)
|
||||
topToolbar = twoSideComponent
|
||||
|
||||
return twoSideComponent
|
||||
}
|
||||
|
||||
private fun isVariablePresentInStack(): Boolean {
|
||||
val values = dataViewerModel.frameAccessor.loadFrame(null) ?: return true
|
||||
for (i in 0 until values.size()) {
|
||||
if (values.getValue(i) == dataViewerModel.debugValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupChangeListener() {
|
||||
dataViewerModel.frameAccessor.addFrameListener(object : PyFrameListener {
|
||||
override fun frameChanged() {
|
||||
dataViewerModel.debugValue ?: return
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
// Could be that in changed frames our value is missing. (PY-66235)
|
||||
if (isVariablePresentInStack()) {
|
||||
updateModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateModel() {
|
||||
val model = model ?: return
|
||||
model.invalidateCache()
|
||||
if (dataViewerModel.isModified) {
|
||||
apply(dataViewerModel.modifiedVarName, true)
|
||||
}
|
||||
else {
|
||||
updateDebugValue(model)
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
if (isShowing()) {
|
||||
model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDebugValue(model: AsyncArrayTableModel) {
|
||||
val oldValue = model.debugValue
|
||||
if (oldValue != null && !oldValue.isTemporary || slicingValueFromUI.isEmpty()) {
|
||||
return
|
||||
}
|
||||
val newValue = getDebugValue(slicingValueFromUI, false)
|
||||
if (newValue != null) {
|
||||
model.debugValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
override fun createTable(originalDebugValue: PyDebugValue?, chunk: ArrayChunk?): AbstractDataViewTable {
|
||||
val mainTable = JBTableWithRowHeaders(PyDataView.isAutoResizeEnabled(dataViewerModel.project))
|
||||
mainTable.scrollPane.border = BorderFactory.createEmptyBorder()
|
||||
|
||||
panelWithTable.apply {
|
||||
add(mainTable.scrollPane, BorderLayout.CENTER)
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
|
||||
table = mainTable
|
||||
return mainTable
|
||||
}
|
||||
|
||||
private fun createEditorField(commandSource: TextFieldCommandSource): EditorTextField {
|
||||
return object : EditorTextField(EditorFactory.getInstance().createDocument(""), dataViewerModel.project, PythonFileType.INSTANCE, false, true) {
|
||||
override fun createEditor(): EditorEx {
|
||||
val editor = super.createEditor()
|
||||
editor.settings.additionalColumnsCount = 5
|
||||
editor.getContentComponent().addKeyListener(object : KeyAdapter() {
|
||||
override fun keyPressed(e: KeyEvent) {
|
||||
if (e.keyCode == KeyEvent.VK_ENTER) {
|
||||
onEnterPressed(commandSource)
|
||||
}
|
||||
}
|
||||
})
|
||||
return editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun updateUI(
|
||||
chunk: ArrayChunk,
|
||||
originalDebugValue: PyDebugValue,
|
||||
strategy: DataViewStrategy,
|
||||
modifier: Boolean,
|
||||
) {
|
||||
errorLabel.visible(false)
|
||||
val debugValue = chunk.value
|
||||
val model = strategy.createTableModel(chunk.rows, chunk.columns, this, debugValue)
|
||||
model.addToCache(chunk)
|
||||
UIUtil.invokeLaterIfNeeded {
|
||||
val table = table ?: createTable()
|
||||
table.setModel(model, modifier)
|
||||
|
||||
updateTabNameSlicingFieldAndFormatField(chunk, originalDebugValue, modifier)
|
||||
|
||||
val cellRenderer = strategy.createCellRenderer(Double.MIN_VALUE, Double.MAX_VALUE, chunk)
|
||||
cellRenderer.setColored(dataViewerModel.protectedColored)
|
||||
model.fireTableDataChanged()
|
||||
model.fireTableCellUpdated(0, 0)
|
||||
if (table.columnCount > 0) {
|
||||
table.setDefaultRenderer(table.getColumnClass(0), cellRenderer)
|
||||
}
|
||||
table.setShowColumns(strategy.showColumnHeader())
|
||||
}
|
||||
}
|
||||
|
||||
fun resize(autoResize: Boolean) {
|
||||
table?.setAutoResize(autoResize)
|
||||
apply(slicingValueFromUI, false)
|
||||
}
|
||||
|
||||
override fun setError(text: @NlsContexts.Label String, modifier: Boolean) {
|
||||
errorLabel.visible(true)
|
||||
errorLabel.text(composeErrorMessage(text, modifier))
|
||||
if (!modifier) {
|
||||
table?.setEmpty()
|
||||
for (listener in listeners) {
|
||||
listener.onNameChanged(PyBundle.message("debugger.data.view.empty.tab"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
val PY_DATA_VIEWER_COMMUNITY_PANEL_KEY: DataKey<AbstractDataViewTable> = DataKey.create<AbstractDataViewTable>("PY_DATA_VIEWER_COMMUNITY_PANEL_KEY")
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.debugger.containerview
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionResultSet
|
||||
import com.intellij.codeInsight.completion.PrioritizedLookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.util.TextFieldCompletionProvider
|
||||
import com.jetbrains.python.debugger.PyDebugValue
|
||||
import com.jetbrains.python.debugger.PyFrameAccessor
|
||||
|
||||
class PyDataViewerModel(
|
||||
val project: Project,
|
||||
val frameAccessor: PyFrameAccessor,
|
||||
) {
|
||||
/**
|
||||
* Represents a formatting string used for specifying format.
|
||||
*
|
||||
* This field is used as a source of truth during switching between community and powerful tables.
|
||||
*/
|
||||
var format: String = ""
|
||||
|
||||
/**
|
||||
* Represents a slicing string used for slicing data in a viewer panel.
|
||||
* For example, np_array_3d[0] or df['column_1'].
|
||||
*
|
||||
* This field is used as a source of truth during switching between community and powerful tables.
|
||||
*/
|
||||
var slicing: String = ""
|
||||
|
||||
/**
|
||||
* This field is used as a source of truth during switching between community and powerful tables.
|
||||
*/
|
||||
var isColored: Boolean = false
|
||||
|
||||
var protectedColored: Boolean = PyDataView.isColoringEnabled(project)
|
||||
|
||||
var originalVarName: @NlsSafe String? = null
|
||||
|
||||
var modifiedVarName: String? = null
|
||||
|
||||
var debugValue: PyDebugValue? = null
|
||||
|
||||
var isModified: Boolean = false
|
||||
|
||||
inner class PyDataViewerCompletionProvider : TextFieldCompletionProvider() {
|
||||
override fun addCompletionVariants(text: String, offset: Int, prefix: String, result: CompletionResultSet) {
|
||||
val values = availableValues.sortedBy { obj: PyDebugValue -> obj.name }
|
||||
for (i in values.indices) {
|
||||
val value = values[i]
|
||||
val element = LookupElementBuilder.create(value.name).withTypeText(value.type, true)
|
||||
result.addElement(PrioritizedLookupElement.withPriority(element, -i.toDouble()))
|
||||
}
|
||||
}
|
||||
|
||||
private val availableValues: List<PyDebugValue>
|
||||
get() {
|
||||
val values: MutableList<PyDebugValue> = ArrayList()
|
||||
try {
|
||||
val list = frameAccessor.loadFrame(null) ?: return values
|
||||
for (i in 0 until list.size()) {
|
||||
val value = list.getValue(i) as PyDebugValue
|
||||
val type = value.type
|
||||
if (DataViewStrategy.getStrategy(type) != null) {
|
||||
values.add(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
thisLogger().error(e)
|
||||
}
|
||||
return values
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,411 +1,123 @@
|
||||
// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.debugger.containerview
|
||||
|
||||
import com.intellij.codeInsight.completion.CompletionResultSet
|
||||
import com.intellij.codeInsight.completion.PrioritizedLookupElement
|
||||
import com.intellij.codeInsight.lookup.LookupElementBuilder
|
||||
import com.intellij.ide.DataManager
|
||||
import com.intellij.openapi.Disposable
|
||||
import com.intellij.openapi.application.ApplicationManager
|
||||
import com.intellij.openapi.diagnostic.thisLogger
|
||||
import com.intellij.openapi.editor.EditorFactory
|
||||
import com.intellij.openapi.editor.ex.EditorEx
|
||||
import com.intellij.openapi.observable.properties.AtomicBooleanProperty
|
||||
import com.intellij.openapi.actionSystem.CompositeDataProvider
|
||||
import com.intellij.openapi.actionSystem.DataKey
|
||||
import com.intellij.openapi.actionSystem.DataProvider
|
||||
import com.intellij.openapi.project.Project
|
||||
import com.intellij.openapi.util.NlsContexts
|
||||
import com.intellij.openapi.util.NlsSafe
|
||||
import com.intellij.ui.EditorTextField
|
||||
import com.intellij.ui.JBColor
|
||||
import com.intellij.ui.dsl.builder.AlignX
|
||||
import com.intellij.ui.dsl.builder.Cell
|
||||
import com.intellij.ui.dsl.builder.panel
|
||||
import com.intellij.ui.dsl.builder.text
|
||||
import com.intellij.util.TextFieldCompletionProvider
|
||||
import com.intellij.util.ui.UIUtil
|
||||
import com.jetbrains.python.PyBundle
|
||||
import com.jetbrains.python.PythonFileType
|
||||
import com.jetbrains.python.debugger.*
|
||||
import com.jetbrains.python.debugger.array.AbstractDataViewTable
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel
|
||||
import com.jetbrains.python.debugger.array.JBTableWithRowHeaders
|
||||
import org.jetbrains.annotations.Nls
|
||||
import com.jetbrains.python.debugger.PyDebugValue
|
||||
import com.jetbrains.python.debugger.PyFrameAccessor
|
||||
import java.awt.BorderLayout
|
||||
import java.awt.event.KeyAdapter
|
||||
import java.awt.event.KeyEvent
|
||||
import java.util.concurrent.CopyOnWriteArrayList
|
||||
import javax.swing.BorderFactory
|
||||
import javax.swing.JEditorPane
|
||||
import javax.swing.JComponent
|
||||
import javax.swing.JPanel
|
||||
|
||||
open class PyDataViewerPanel(@JvmField protected val project: Project, val frameAccessor: PyFrameAccessor) :
|
||||
JPanel(BorderLayout()), Disposable {
|
||||
class PyDataViewerPanel(
|
||||
val project: Project,
|
||||
val frameAccessor: PyFrameAccessor,
|
||||
private var isPanelFromFactory: Boolean = false,
|
||||
) : JPanel(BorderLayout()), Disposable {
|
||||
|
||||
protected val tablePanel: JPanel = JPanel(BorderLayout())
|
||||
|
||||
protected var table: AbstractDataViewTable? = null
|
||||
|
||||
protected val sliceTextFieldOldTable: EditorTextField = createEditorField(TextFieldCommandSource.SLICING)
|
||||
|
||||
protected var formatTextFieldOldTable: EditorTextField = createEditorField(TextFieldCommandSource.FORMATTING)
|
||||
|
||||
private var colored: Boolean = PyDataView.isColoringEnabled(project)
|
||||
|
||||
private val listeners = CopyOnWriteArrayList<Listener>()
|
||||
|
||||
var originalVarName: @NlsSafe String? = null
|
||||
private set
|
||||
|
||||
protected var modifiedVarName: String? = null
|
||||
|
||||
protected var debugValue: PyDebugValue? = null
|
||||
|
||||
/**
|
||||
* Represents a formatting string used for specifying format.
|
||||
*
|
||||
* This field is needed to synchronize old and new tables
|
||||
* regarding the actual formatting value.
|
||||
*/
|
||||
open var format: String = ""
|
||||
get() {
|
||||
val format = formatTextFieldOldTable.getText()
|
||||
return format.ifEmpty { "%s" }
|
||||
}
|
||||
protected set(value) {
|
||||
field = value
|
||||
formatTextFieldOldTable.text = value
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a slicing string used for slicing data in a viewer panel.
|
||||
* For example, numpy_array[:, 0] or df['column_1'].
|
||||
*
|
||||
* This field is needed to synchronize old and new tables
|
||||
* regarding the actual slicing value.
|
||||
*/
|
||||
open var slicing: String = ""
|
||||
get() {
|
||||
val slicing = sliceTextFieldOldTable.getText()
|
||||
return slicing.ifEmpty { "None" }
|
||||
}
|
||||
protected set(value) {
|
||||
field = value
|
||||
sliceTextFieldOldTable.text = value
|
||||
}
|
||||
|
||||
var isColored: Boolean
|
||||
get() = colored
|
||||
set(state) {
|
||||
colored = state
|
||||
val table = table
|
||||
if (table != null && !table.isEmpty) {
|
||||
(table.getDefaultRenderer(table.getColumnClass(0)) as ColoredCellRenderer).setColored(state)
|
||||
table.repaint()
|
||||
}
|
||||
}
|
||||
|
||||
private val model: AsyncArrayTableModel?
|
||||
get() {
|
||||
return table?.model as? AsyncArrayTableModel
|
||||
}
|
||||
|
||||
var isModified: Boolean = false
|
||||
protected set
|
||||
|
||||
private lateinit var errorLabel: Cell<JEditorPane>
|
||||
|
||||
protected val isSlicingAndFormattingOldPanelsVisible: AtomicBooleanProperty = AtomicBooleanProperty(true)
|
||||
var component: PyDataViewerAbstractPanel
|
||||
|
||||
init {
|
||||
PyDataViewCompletionProvider().apply(sliceTextFieldOldTable)
|
||||
val dataViewerModel = PyDataViewerModel(project, frameAccessor)
|
||||
|
||||
add(tablePanel, BorderLayout.CENTER)
|
||||
add(panel {
|
||||
row {
|
||||
cell(sliceTextFieldOldTable).align(AlignX.FILL).resizableColumn()
|
||||
label(PyBundle.message("form.data.viewer.format"))
|
||||
cell(formatTextFieldOldTable)
|
||||
}.visibleIf(isSlicingAndFormattingOldPanelsVisible)
|
||||
row {
|
||||
errorLabel = text("").apply { component.setForeground(JBColor.RED) }
|
||||
}
|
||||
}, BorderLayout.SOUTH)
|
||||
|
||||
setupChangeListener()
|
||||
}
|
||||
|
||||
override fun dispose(): Unit = Unit
|
||||
|
||||
private fun isVariablePresentInStack(): Boolean {
|
||||
val values = frameAccessor.loadFrame(null) ?: return true
|
||||
for (i in 0 until values.size()) {
|
||||
if (values.getValue(i) == debugValue) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
private fun setupChangeListener() {
|
||||
frameAccessor.addFrameListener(object : PyFrameListener {
|
||||
override fun frameChanged() {
|
||||
debugValue ?: return
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
// Could be that in changed frames our value is missing. (PY-66235)
|
||||
if (isVariablePresentInStack()) {
|
||||
updateModel()
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private fun updateModel() {
|
||||
val model = model ?: return
|
||||
model.invalidateCache()
|
||||
if (isModified) {
|
||||
apply(modifiedVarName, true)
|
||||
var dataViewerPanel = if (isPanelsFromFactoryAvailable()) {
|
||||
isPanelFromFactory = true
|
||||
createPanelFromFactory(dataViewerModel)
|
||||
}
|
||||
else {
|
||||
updateDebugValue(model)
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
if (isShowing()) {
|
||||
model.fireTableDataChanged()
|
||||
}
|
||||
}
|
||||
PyDataViewerCommunityPanel(dataViewerModel)
|
||||
}
|
||||
|
||||
component = dataViewerPanel
|
||||
add(component)
|
||||
|
||||
setupDataProvider()
|
||||
|
||||
if (isPanelsFromFactoryAvailable() && !isPanelFromFactory) {
|
||||
PyDataViewerPanelHelper.createGotItTooltip(tablePanel = component, tableParentPanel = this)
|
||||
}
|
||||
}
|
||||
|
||||
private fun updateDebugValue(model: AsyncArrayTableModel) {
|
||||
val oldValue = model.debugValue
|
||||
if (oldValue != null && !oldValue.isTemporary || slicing.isEmpty()) {
|
||||
fun getDataViewerModel(): PyDataViewerModel {
|
||||
return component.dataViewerModel
|
||||
}
|
||||
|
||||
fun addListener(onNameChangedListener: PyDataViewerAbstractPanel.OnNameChangedListener) {
|
||||
component.addListener(onNameChangedListener)
|
||||
}
|
||||
|
||||
fun switchBetweenCommunityAndFactoriesTables() {
|
||||
isPanelFromFactory = !isPanelFromFactory
|
||||
|
||||
val dataViewerModel = getDataViewerModel()
|
||||
val debugValue = dataViewerModel.debugValue
|
||||
if (debugValue != null && !isSupportedByCommunityDataViewer(debugValue)) {
|
||||
isPanelFromFactory = true
|
||||
PyDataViewerPanelHelper.showCommunityDataViewerRestrictionsBalloon(tablePanel = component, tableParentPanel = this)
|
||||
return
|
||||
}
|
||||
val newValue = getDebugValue(slicing, false, false)
|
||||
if (newValue != null) {
|
||||
model.debugValue = newValue
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun getOrCreateMainTable(): AbstractDataViewTable {
|
||||
val mainTable = JBTableWithRowHeaders(PyDataView.isAutoResizeEnabled(project))
|
||||
mainTable.scrollPane.border = BorderFactory.createEmptyBorder()
|
||||
tablePanel.apply {
|
||||
add(mainTable.scrollPane, BorderLayout.CENTER)
|
||||
revalidate()
|
||||
repaint()
|
||||
}
|
||||
table = mainTable
|
||||
return mainTable
|
||||
}
|
||||
|
||||
private fun createEditorField(commandSource: TextFieldCommandSource): EditorTextField {
|
||||
return object : EditorTextField(EditorFactory.getInstance().createDocument(""), project, PythonFileType.INSTANCE, false, true) {
|
||||
override fun createEditor(): EditorEx {
|
||||
val editor = super.createEditor()
|
||||
editor.settings.additionalColumnsCount = 5
|
||||
editor.getContentComponent().addKeyListener(object : KeyAdapter() {
|
||||
override fun keyPressed(e: KeyEvent) {
|
||||
if (e.keyCode == KeyEvent.VK_ENTER) {
|
||||
onEnterPressed(commandSource)
|
||||
}
|
||||
}
|
||||
})
|
||||
return editor
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun onEnterPressed(commandSource: TextFieldCommandSource) {
|
||||
apply(slicing, false, commandSource)
|
||||
}
|
||||
|
||||
fun apply(name: String?, modifier: Boolean, commandSource: TextFieldCommandSource? = null) {
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
val debugValue = getDebugValue(name, true, modifier)
|
||||
ApplicationManager.getApplication().invokeLater { debugValue?.let { apply(it, modifier, commandSource) } }
|
||||
}
|
||||
}
|
||||
|
||||
open fun apply(debugValue: PyDebugValue, modifier: Boolean, commandSource: TextFieldCommandSource? = null) {
|
||||
errorLabel.visible(false)
|
||||
val type = debugValue.type
|
||||
val strategy = DataViewStrategy.getStrategy(type)
|
||||
if (strategy == null) {
|
||||
setError(PyBundle.message("debugger.data.view.type.is.not.supported", type), modifier)
|
||||
return
|
||||
}
|
||||
ApplicationManager.getApplication().executeOnPooledThread {
|
||||
try {
|
||||
doStrategyInitExecution(debugValue.frameAccessor, strategy)
|
||||
// Currently does not support pandas dataframes.
|
||||
val arrayChunk = debugValue.frameAccessor.getArrayItems(debugValue, 0, 0, 0, 0, format)
|
||||
ApplicationManager.getApplication().invokeLater {
|
||||
updateUI(arrayChunk, debugValue, strategy, modifier)
|
||||
isModified = modifier
|
||||
this.debugValue = debugValue
|
||||
}
|
||||
}
|
||||
catch (e: IllegalArgumentException) {
|
||||
ApplicationManager.getApplication().invokeLater { setError(e.localizedMessage, modifier) } //NON-NLS
|
||||
}
|
||||
catch (e: PyDebuggerException) {
|
||||
thisLogger().error(e)
|
||||
}
|
||||
catch (e: Exception) {
|
||||
if (e.message?.let { "Numpy is not available" in it } == true) {
|
||||
setError(PyBundle.message("debugger.data.view.numpy.is.not.available", type), modifier)
|
||||
}
|
||||
thisLogger().error(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(PyDebuggerException::class)
|
||||
protected open fun doStrategyInitExecution(frameAccessor: PyFrameAccessor, strategy: DataViewStrategy): Unit = Unit
|
||||
|
||||
// Chunk currently could be null when we are trying to view, for example, pandas dataframe.
|
||||
protected fun updateTabNameAndSliceField(chunk: ArrayChunk?, originalDebugValue: PyDebugValue, modifier: Boolean) {
|
||||
// Debugger generates a temporary name for every slice evaluation, so we should select a correct name for it
|
||||
val debugValue = chunk?.value
|
||||
val realName = if (debugValue == null || debugValue.name == originalDebugValue.tempName) originalDebugValue.name else chunk.slicePresentation
|
||||
var shownName = realName
|
||||
if (modifier && originalVarName != shownName) {
|
||||
@Suppress("HardCodedStringLiteral") // This is just format like %s and cannot be i18.
|
||||
shownName = String.format(MODIFIED_VARIABLE_FORMAT, originalVarName)
|
||||
component = if (isPanelFromFactory && isPanelsFromFactoryAvailable()) {
|
||||
createPanelFromFactory(dataViewerModel)
|
||||
}
|
||||
else {
|
||||
originalVarName = realName
|
||||
PyDataViewerCommunityPanel(dataViewerModel)
|
||||
}
|
||||
originalVarName?.let { slicing = it }
|
||||
component.recreateTable()
|
||||
|
||||
// Modifier flag means that variable changes are temporary
|
||||
modifiedVarName = realName
|
||||
if (sliceTextFieldOldTable.editor != null) {
|
||||
sliceTextFieldOldTable.getCaretModel().moveToOffset(originalVarName!!.length)
|
||||
}
|
||||
for (listener in listeners) {
|
||||
listener.onNameChanged(shownName)
|
||||
}
|
||||
removeAll()
|
||||
add(component)
|
||||
|
||||
if (chunk != null) {
|
||||
format = chunk.format
|
||||
if (debugValue != null) {
|
||||
component.apply(debugValue, false)
|
||||
}
|
||||
else {
|
||||
component.apply(dataViewerModel.slicing, false)
|
||||
}
|
||||
}
|
||||
|
||||
protected open fun updateUI(
|
||||
chunk: ArrayChunk, originalDebugValue: PyDebugValue,
|
||||
strategy: DataViewStrategy, modifier: Boolean,
|
||||
) {
|
||||
val debugValue = chunk.value
|
||||
val model = strategy.createTableModel(chunk.rows, chunk.columns, this, debugValue)
|
||||
model.addToCache(chunk)
|
||||
UIUtil.invokeLaterIfNeeded {
|
||||
val table = table ?: getOrCreateMainTable()
|
||||
table.setModel(model, modifier)
|
||||
|
||||
updateTabNameAndSliceField(chunk, originalDebugValue, modifier)
|
||||
|
||||
val cellRenderer = strategy.createCellRenderer(Double.MIN_VALUE, Double.MAX_VALUE, chunk)
|
||||
cellRenderer.setColored(colored)
|
||||
model.fireTableDataChanged()
|
||||
model.fireTableCellUpdated(0, 0)
|
||||
if (table.columnCount > 0) {
|
||||
table.setDefaultRenderer(table.getColumnClass(0), cellRenderer)
|
||||
}
|
||||
table.setShowColumns(strategy.showColumnHeader())
|
||||
}
|
||||
private fun isPanelsFromFactoryAvailable(): Boolean {
|
||||
return PyDataViewPanelFactory.EP_NAME.extensionList.isNotEmpty()
|
||||
}
|
||||
|
||||
private fun getDebugValue(expression: @NlsSafe String?, pooledThread: Boolean, modifier: Boolean): PyDebugValue? {
|
||||
return try {
|
||||
val value = frameAccessor.evaluate(expression, false, true)
|
||||
if (value == null || value.isErrorOnEval) {
|
||||
val runnable = Runnable {
|
||||
setError(if (value != null && value.value != null) value.value!!
|
||||
else PyBundle.message("debugger.data.view.failed.to.evaluate.expression", expression), modifier)
|
||||
}
|
||||
if (pooledThread) {
|
||||
ApplicationManager.getApplication().invokeLater(runnable)
|
||||
}
|
||||
else {
|
||||
runnable.run()
|
||||
}
|
||||
return null
|
||||
}
|
||||
value
|
||||
}
|
||||
catch (e: PyDebuggerException) {
|
||||
val runnable = Runnable { setError(e.getTracebackError(), modifier) } //NON-NLS
|
||||
if (pooledThread) {
|
||||
ApplicationManager.getApplication().invokeLater(runnable)
|
||||
}
|
||||
else {
|
||||
runnable.run()
|
||||
}
|
||||
null
|
||||
}
|
||||
private fun createPanelFromFactory(dataViewerModel: PyDataViewerModel): PyDataViewerAbstractPanel {
|
||||
return PyDataViewPanelFactory.EP_NAME.extensionList.first().createDataViewerPanel(dataViewerModel)
|
||||
}
|
||||
|
||||
fun resize(autoResize: Boolean) {
|
||||
table?.setAutoResize(autoResize)
|
||||
apply(slicing, false)
|
||||
/**
|
||||
* Old tables cannot work properly with polars dataframes.
|
||||
*/
|
||||
private fun isSupportedByCommunityDataViewer(debugValue: PyDebugValue): Boolean {
|
||||
return debugValue.typeQualifier != "polars.dataframe.frame"
|
||||
}
|
||||
|
||||
open fun setError(text: @NlsContexts.Label String, modifier: Boolean) {
|
||||
errorLabel.visible(true)
|
||||
errorLabel.text(composeErrorMessage(text, modifier))
|
||||
if (!modifier) {
|
||||
table?.setEmpty()
|
||||
for (listener in listeners) {
|
||||
listener.onNameChanged(PyBundle.message("debugger.data.view.empty.tab"))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected fun composeErrorMessage(text: @NlsContexts.Label String, modifier: Boolean): @Nls String {
|
||||
return if (modifier) PyBundle.message("debugger.dataviewer.modifier.error", text) else text
|
||||
}
|
||||
|
||||
fun addListener(listener: Listener) {
|
||||
listeners.add(listener)
|
||||
}
|
||||
|
||||
fun interface Listener {
|
||||
fun onNameChanged(name: @NlsContexts.TabTitle String)
|
||||
}
|
||||
|
||||
protected inner class PyDataViewCompletionProvider : TextFieldCompletionProvider() {
|
||||
override fun addCompletionVariants(text: String, offset: Int, prefix: String, result: CompletionResultSet) {
|
||||
val values = availableValues.sortedBy { obj: PyDebugValue -> obj.name }
|
||||
for (i in values.indices) {
|
||||
val value = values[i]
|
||||
val element = LookupElementBuilder.create(value.name).withTypeText(value.type, true)
|
||||
result.addElement(PrioritizedLookupElement.withPriority(element, -i.toDouble()))
|
||||
}
|
||||
private fun setupDataProvider() {
|
||||
val toolbarDataProvider = DataProvider { dataId ->
|
||||
if (PY_DATA_VIEWER_PANEL_KEY.`is`(dataId)) this@PyDataViewerPanel else null
|
||||
}
|
||||
|
||||
private val availableValues: List<PyDebugValue>
|
||||
get() {
|
||||
val values: MutableList<PyDebugValue> = ArrayList()
|
||||
try {
|
||||
val list = frameAccessor.loadFrame(null) ?: return values
|
||||
for (i in 0 until list.size()) {
|
||||
val value = list.getValue(i) as PyDebugValue
|
||||
val type = value.type
|
||||
if (DataViewStrategy.getStrategy(type) != null) {
|
||||
values.add(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
catch (e: Exception) {
|
||||
thisLogger().error(e)
|
||||
}
|
||||
return values
|
||||
}
|
||||
addDataProvider(this, toolbarDataProvider)
|
||||
}
|
||||
|
||||
open fun closeEditorTabs(): Unit = Unit
|
||||
override fun dispose() {}
|
||||
|
||||
companion object {
|
||||
private const val MODIFIED_VARIABLE_FORMAT = "%s*"
|
||||
|
||||
val PY_DATA_VIEWER_PANEL_KEY: DataKey<PyDataViewerPanel> = DataKey.create<PyDataViewerPanel>("PY_DATA_VIEWER_PANEL_KEY")
|
||||
|
||||
fun addDataProvider(component: JComponent, provider: DataProvider) {
|
||||
val currentProvider = DataManager.getDataProvider(component)
|
||||
if (currentProvider != null) {
|
||||
DataManager.removeDataProvider(component)
|
||||
DataManager.registerDataProvider(component, CompositeDataProvider.compose(currentProvider, provider))
|
||||
}
|
||||
else {
|
||||
DataManager.registerDataProvider(component, provider)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,51 @@
|
||||
// Copyright 2000-2025 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
|
||||
package com.jetbrains.python.debugger.containerview
|
||||
|
||||
import com.intellij.icons.AllIcons
|
||||
import com.intellij.openapi.ui.MessageType
|
||||
import com.intellij.openapi.ui.popup.Balloon
|
||||
import com.intellij.openapi.ui.popup.JBPopupFactory
|
||||
import com.intellij.ui.GotItTooltip
|
||||
import com.intellij.ui.awt.RelativePoint
|
||||
import com.intellij.ui.util.preferredHeight
|
||||
import com.intellij.ui.util.preferredWidth
|
||||
import com.jetbrains.python.PyBundle
|
||||
import java.awt.Point
|
||||
|
||||
internal object PyDataViewerPanelHelper {
|
||||
fun showCommunityDataViewerRestrictionsBalloon(tablePanel: PyDataViewerAbstractPanel, tableParentPanel: PyDataViewerPanel) {
|
||||
createBalloon(tablePanel)
|
||||
.show(
|
||||
RelativePoint(
|
||||
tableParentPanel,
|
||||
Point(tableParentPanel.width - (tablePanel.topToolbar?.preferredWidth ?: 10) / 2, tablePanel.topToolbar?.preferredHeight ?: 0)
|
||||
),
|
||||
Balloon.Position.below
|
||||
)
|
||||
}
|
||||
|
||||
private fun createBalloon(component: PyDataViewerAbstractPanel): Balloon {
|
||||
return JBPopupFactory.getInstance()
|
||||
.createHtmlTextBalloonBuilder(PyBundle.message("debugger.dataViewer.dataframes.unsupported"), MessageType.INFO, null).apply {
|
||||
setDisposable(component)
|
||||
setHideOnAction(true)
|
||||
setHideOnClickOutside(true)
|
||||
}.createBalloon()
|
||||
}
|
||||
|
||||
/**
|
||||
* GotIt Tooltip on the button with advertising of new and old table modes for viewing arrays in debug.
|
||||
*/
|
||||
fun createGotItTooltip(tablePanel: PyDataViewerAbstractPanel, tableParentPanel: PyDataViewerPanel) {
|
||||
val tooltip = GotItTooltip("py.data.view.new.table",
|
||||
PyBundle.message("debugger.dataViewer.switch.between.tables.gotIt.text"),
|
||||
tableParentPanel)
|
||||
.withLink(PyBundle.message("debugger.dataViewer.switch.between.tables.gotIt.link")) { tableParentPanel.switchBetweenCommunityAndFactoriesTables() }
|
||||
.withShowCount(1)
|
||||
.withIcon(AllIcons.General.BalloonInformation)
|
||||
|
||||
tooltip.show(tablePanel.topToolbar!!) { component, _ ->
|
||||
Point(component.width - component.height / 2, component.height)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -6,7 +6,7 @@ import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerPanel;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerCommunityPanel;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
@@ -20,23 +20,23 @@ public class DataFrameTableModel extends AsyncArrayTableModel {
|
||||
|
||||
public DataFrameTableModel(int rows,
|
||||
int columns,
|
||||
PyDataViewerPanel dataProvider,
|
||||
PyDataViewerCommunityPanel dataProvider,
|
||||
PyDebugValue debugValue,
|
||||
DataFrameViewStrategy strategy) {
|
||||
super(rows, columns, dataProvider, debugValue, strategy);
|
||||
myRowHeaderModel = new RowHeaderModel();
|
||||
}
|
||||
/* we use labels for the first column so we need to offset columns by one everywhere */
|
||||
/* we use labels for the first column, so we need to offset columns by one everywhere */
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int col) {
|
||||
|
||||
Object value = super.getValueAt(row, col);
|
||||
if (value == AsyncArrayTableModel.EMPTY_CELL_VALUE) {
|
||||
if (value == EMPTY_CELL_VALUE) {
|
||||
return value;
|
||||
}
|
||||
TableValueDescriptor descriptor = createValueWithDescriptor(col, value);
|
||||
return descriptor != null ? descriptor : AsyncArrayTableModel.EMPTY_CELL_VALUE;
|
||||
return descriptor != null ? descriptor : EMPTY_CELL_VALUE;
|
||||
}
|
||||
|
||||
private TableValueDescriptor createValueWithDescriptor(int frameCol, Object value) {
|
||||
|
||||
@@ -7,7 +7,7 @@ import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.ColumnFilter;
|
||||
import com.jetbrains.python.debugger.containerview.DataViewStrategy;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerPanel;
|
||||
import com.jetbrains.python.debugger.containerview.PyDataViewerCommunityPanel;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
@@ -35,7 +35,7 @@ public class DataFrameViewStrategy extends DataViewStrategy {
|
||||
@Override
|
||||
public AsyncArrayTableModel createTableModel(int rowCount,
|
||||
int columnCount,
|
||||
@NotNull PyDataViewerPanel dataProvider,
|
||||
@NotNull PyDataViewerCommunityPanel dataProvider,
|
||||
@NotNull PyDebugValue debugValue) {
|
||||
return new DataFrameTableModel(rowCount, columnCount, dataProvider, debugValue, this);
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package com.jetbrains.python.debugger.statistics
|
||||
|
||||
import com.intellij.internal.statistic.eventLog.EventLogGroup
|
||||
import com.intellij.internal.statistic.eventLog.events.EventFields
|
||||
import com.intellij.internal.statistic.eventLog.events.EventId1
|
||||
import com.intellij.internal.statistic.eventLog.events.RoundedIntEventField
|
||||
import com.intellij.internal.statistic.service.fus.collectors.CounterUsagesCollector
|
||||
import com.intellij.openapi.project.Project
|
||||
@@ -25,8 +26,8 @@ object PyDataViewerCollector : CounterUsagesCollector() {
|
||||
ROWS_COUNT_FIELD,
|
||||
COLUMNS_COUNT_FIELD,
|
||||
IS_NEW_TABLE_FIELD)
|
||||
val SLICING_APPLIED_EVENT = GROUP.registerEvent("slicing.applied", IS_NEW_TABLE_FIELD)
|
||||
val FORMATTING_APPLIED_EVENT = GROUP.registerEvent("formatting.applied", IS_NEW_TABLE_FIELD)
|
||||
val SLICING_APPLIED_EVENT: EventId1<Boolean> = GROUP.registerEvent("slicing.applied", IS_NEW_TABLE_FIELD)
|
||||
val FORMATTING_APPLIED_EVENT: EventId1<Boolean> = GROUP.registerEvent("formatting.applied", IS_NEW_TABLE_FIELD)
|
||||
|
||||
enum class DataType(private val typeName: String?) {
|
||||
ARRAY("ndarray"),
|
||||
@@ -68,7 +69,7 @@ object PyDataViewerCollector : CounterUsagesCollector() {
|
||||
}
|
||||
}
|
||||
|
||||
override fun getGroup() = GROUP
|
||||
override fun getGroup(): EventLogGroup = GROUP
|
||||
|
||||
fun logDataOpened(
|
||||
project: Project?,
|
||||
|
||||
Reference in New Issue
Block a user