mirror of
https://gitflic.ru/project/openide/openide.git
synced 2026-04-20 13:31:28 +07:00
Support pandas dataframe in array viewer during debugging (PY-14330)
(cherry picked from commit 669292aa3fb3678411d5b28ce4756ca102de98f0)
This commit is contained in:
committed by
Dmitry Trofimov
parent
9b114a193f
commit
bc8f2ee363
@@ -434,20 +434,9 @@ class BaseInterpreterInterface:
|
||||
return xml
|
||||
|
||||
def getArray(self, attr, roffset, coffset, rows, cols, format):
|
||||
xml = "<xml>"
|
||||
name = attr.split("\t")[-1]
|
||||
array = pydevd_vars.eval_in_context(name, self.get_namespace(), self.get_namespace())
|
||||
|
||||
array, metaxml, r, c, f = pydevd_vars.array_to_meta_xml(array, name, format)
|
||||
xml += metaxml
|
||||
format = '%' + f
|
||||
if rows == -1 and cols == -1:
|
||||
rows = r
|
||||
cols = c
|
||||
xml += pydevd_vars.array_to_xml(array, roffset, coffset, rows, cols, format)
|
||||
xml += "</xml>"
|
||||
|
||||
return xml
|
||||
return pydevd_vars.table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format)
|
||||
|
||||
def evaluate(self, expression):
|
||||
xml = "<xml>"
|
||||
|
||||
@@ -988,17 +988,7 @@ class InternalGetArray(InternalThreadCommand):
|
||||
try:
|
||||
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
|
||||
var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals)
|
||||
|
||||
xml = "<xml>"
|
||||
|
||||
var, metaxml, rows, cols, format = pydevd_vars.array_to_meta_xml(var, self.name, self.format)
|
||||
xml += metaxml
|
||||
self.format = '%' + format
|
||||
if self.rows == -1 and self.cols == -1:
|
||||
self.rows = rows
|
||||
self.cols = cols
|
||||
xml += pydevd_vars.array_to_xml(var, self.roffset, self.coffset, self.rows, self.cols, self.format)
|
||||
xml += "</xml>"
|
||||
xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format )
|
||||
cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml)
|
||||
dbg.writer.add_command(cmd)
|
||||
except:
|
||||
|
||||
@@ -2,8 +2,8 @@
|
||||
resolution/conversion to XML.
|
||||
"""
|
||||
import pickle
|
||||
from _pydevd_bundle.pydevd_constants import * #@UnusedWildImport
|
||||
from types import * #@UnusedWildImport
|
||||
from _pydevd_bundle.pydevd_constants import * # @UnusedWildImport
|
||||
from types import * # @UnusedWildImport
|
||||
|
||||
from _pydevd_bundle.pydevd_custom_frames import get_custom_frame
|
||||
from _pydevd_bundle.pydevd_xml import *
|
||||
@@ -13,7 +13,7 @@ try:
|
||||
from StringIO import StringIO
|
||||
except ImportError:
|
||||
from io import StringIO
|
||||
import sys #@Reimport
|
||||
import sys # @Reimport
|
||||
|
||||
from _pydev_imps._pydev_saved_modules import threading
|
||||
import traceback
|
||||
@@ -22,25 +22,28 @@ from _pydev_bundle.pydev_imports import Exec, quote, execfile
|
||||
from _pydevd_bundle.pydevd_utils import to_string
|
||||
|
||||
|
||||
|
||||
#-------------------------------------------------------------------------- defining true and false for earlier versions
|
||||
# -------------------------------------------------------------------------- defining true and false for earlier versions
|
||||
|
||||
try:
|
||||
__setFalse = False
|
||||
except:
|
||||
import __builtin__
|
||||
|
||||
setattr(__builtin__, 'True', 1)
|
||||
setattr(__builtin__, 'False', 0)
|
||||
|
||||
#------------------------------------------------------------------------------------------------------ class for errors
|
||||
|
||||
class VariableError(RuntimeError):pass
|
||||
# ------------------------------------------------------------------------------------------------------ class for errors
|
||||
|
||||
class VariableError(RuntimeError): pass
|
||||
|
||||
|
||||
class FrameNotFoundError(RuntimeError): pass
|
||||
|
||||
class FrameNotFoundError(RuntimeError):pass
|
||||
|
||||
def _iter_frames(initialFrame):
|
||||
'''NO-YIELD VERSION: Iterates through all the frames starting at the specified frame (which will be the first returned item)'''
|
||||
#cannot use yield
|
||||
# cannot use yield
|
||||
frames = []
|
||||
|
||||
while initialFrame is not None:
|
||||
@@ -49,6 +52,7 @@ def _iter_frames(initialFrame):
|
||||
|
||||
return frames
|
||||
|
||||
|
||||
def dump_frames(thread_id):
|
||||
sys.stdout.write('dumping frames\n')
|
||||
if thread_id != get_thread_id(threading.currentThread()):
|
||||
@@ -59,21 +63,25 @@ def dump_frames(thread_id):
|
||||
sys.stdout.write('%s\n' % pickle.dumps(frame))
|
||||
|
||||
|
||||
#===============================================================================
|
||||
# ===============================================================================
|
||||
# AdditionalFramesContainer
|
||||
#===============================================================================
|
||||
# ===============================================================================
|
||||
class AdditionalFramesContainer:
|
||||
lock = thread.allocate_lock()
|
||||
additional_frames = {} #dict of dicts
|
||||
additional_frames = {} # dict of dicts
|
||||
|
||||
|
||||
def add_additional_frame_by_id(thread_id, frames_by_id):
|
||||
AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id
|
||||
addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility
|
||||
|
||||
|
||||
addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility
|
||||
|
||||
|
||||
def remove_additional_frame_by_id(thread_id):
|
||||
del AdditionalFramesContainer.additional_frames[thread_id]
|
||||
|
||||
|
||||
removeAdditionalFrameById = remove_additional_frame_by_id # Backward compatibility
|
||||
|
||||
|
||||
@@ -89,9 +97,9 @@ def find_frame(thread_id, frame_id):
|
||||
""" returns a frame on the thread that has a given frame_id """
|
||||
try:
|
||||
curr_thread_id = get_thread_id(threading.currentThread())
|
||||
if thread_id != curr_thread_id :
|
||||
if thread_id != curr_thread_id:
|
||||
try:
|
||||
return get_custom_frame(thread_id, frame_id) #I.e.: thread_id could be a stackless frame id + thread_id.
|
||||
return get_custom_frame(thread_id, frame_id) # I.e.: thread_id could be a stackless frame id + thread_id.
|
||||
except:
|
||||
pass
|
||||
|
||||
@@ -120,12 +128,12 @@ def find_frame(thread_id, frame_id):
|
||||
|
||||
del frame
|
||||
|
||||
#Important: python can hold a reference to the frame from the current context
|
||||
#if an exception is raised, so, if we don't explicitly add those deletes
|
||||
#we might have those variables living much more than we'd want to.
|
||||
# Important: python can hold a reference to the frame from the current context
|
||||
# if an exception is raised, so, if we don't explicitly add those deletes
|
||||
# we might have those variables living much more than we'd want to.
|
||||
|
||||
#I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
|
||||
#need to call sys.exc_clear())
|
||||
# I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
|
||||
# need to call sys.exc_clear())
|
||||
del curFrame
|
||||
|
||||
if frameFound is None:
|
||||
@@ -155,6 +163,7 @@ def find_frame(thread_id, frame_id):
|
||||
traceback.print_exc()
|
||||
return None
|
||||
|
||||
|
||||
def getVariable(thread_id, frame_id, scope, attrs):
|
||||
"""
|
||||
returns the value of a variable
|
||||
@@ -170,14 +179,14 @@ def getVariable(thread_id, frame_id, scope, attrs):
|
||||
not the frame (as we don't care about the frame in this case).
|
||||
"""
|
||||
if scope == 'BY_ID':
|
||||
if thread_id != get_thread_id(threading.currentThread()) :
|
||||
if thread_id != get_thread_id(threading.currentThread()):
|
||||
raise VariableError("getVariable: must execute on same thread")
|
||||
|
||||
try:
|
||||
import gc
|
||||
objects = gc.get_objects()
|
||||
except:
|
||||
pass #Not all python variants have it.
|
||||
pass # Not all python variants have it.
|
||||
else:
|
||||
frame_id = int(frame_id)
|
||||
for var in objects:
|
||||
@@ -190,7 +199,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
|
||||
|
||||
return var
|
||||
|
||||
#If it didn't return previously, we coudn't find it by id (i.e.: alrceady garbage collected).
|
||||
# If it didn't return previously, we coudn't find it by id (i.e.: alrceady garbage collected).
|
||||
sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,))
|
||||
return None
|
||||
|
||||
@@ -328,37 +337,38 @@ def evaluate_expression(thread_id, frame_id, expression, doExec):
|
||||
if frame is None:
|
||||
return
|
||||
|
||||
#Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
|
||||
#(Names not resolved in generator expression in method)
|
||||
#See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
|
||||
# Not using frame.f_globals because of https://sourceforge.net/tracker2/?func=detail&aid=2541355&group_id=85796&atid=577329
|
||||
# (Names not resolved in generator expression in method)
|
||||
# See message: http://mail.python.org/pipermail/python-list/2009-January/526522.html
|
||||
updated_globals = {}
|
||||
updated_globals.update(frame.f_globals)
|
||||
updated_globals.update(frame.f_locals) #locals later because it has precedence over the actual globals
|
||||
updated_globals.update(frame.f_locals) # locals later because it has precedence over the actual globals
|
||||
|
||||
try:
|
||||
expression = str(expression.replace('@LINE@', '\n'))
|
||||
|
||||
if doExec:
|
||||
try:
|
||||
#try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
|
||||
#it will have whatever the user actually did)
|
||||
# try to make it an eval (if it is an eval we can print it, otherwise we'll exec it and
|
||||
# it will have whatever the user actually did)
|
||||
compiled = compile(expression, '<string>', 'eval')
|
||||
except:
|
||||
Exec(expression, updated_globals, frame.f_locals)
|
||||
pydevd_save_locals.save_locals(frame)
|
||||
else:
|
||||
result = eval(compiled, updated_globals, frame.f_locals)
|
||||
if result is not None: #Only print if it's not None (as python does)
|
||||
if result is not None: # Only print if it's not None (as python does)
|
||||
sys.stdout.write('%s\n' % (result,))
|
||||
return
|
||||
|
||||
else:
|
||||
return eval_in_context(expression, updated_globals, frame.f_locals)
|
||||
finally:
|
||||
#Should not be kept alive if an exception happens and this frame is kept in the stack.
|
||||
# Should not be kept alive if an exception happens and this frame is kept in the stack.
|
||||
del updated_globals
|
||||
del frame
|
||||
|
||||
|
||||
def change_attr_expression(thread_id, frame_id, attr, expression, dbg):
|
||||
'''Changes some attribute in a given frame.
|
||||
'''
|
||||
@@ -385,7 +395,7 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg):
|
||||
pydevd_save_locals.save_locals(frame)
|
||||
return frame.f_locals[attr]
|
||||
|
||||
#default way (only works for changing it in the topmost frame)
|
||||
# default way (only works for changing it in the topmost frame)
|
||||
result = eval(expression, frame.f_globals, frame.f_locals)
|
||||
Exec('%s=%s' % (attr, expression), frame.f_globals, frame.f_locals)
|
||||
return result
|
||||
@@ -394,16 +404,35 @@ def change_attr_expression(thread_id, frame_id, attr, expression, dbg):
|
||||
except Exception:
|
||||
traceback.print_exc()
|
||||
|
||||
|
||||
MAXIMUM_ARRAY_SIZE = 100
|
||||
MAX_SLICE_SIZE = 1000
|
||||
|
||||
|
||||
def table_like_struct_to_xml(array, name, roffset, coffset, rows, cols, format):
|
||||
_, type_name, _ = get_type(array)
|
||||
if type_name == 'ndarray':
|
||||
array, metaxml, r, c, f = array_to_meta_xml(array, name, format)
|
||||
xml = metaxml
|
||||
format = '%' + f
|
||||
if rows == -1 and cols == -1:
|
||||
rows = r
|
||||
cols = c
|
||||
xml += array_to_xml(array, roffset, coffset, rows, cols, format)
|
||||
elif type_name == 'DataFrame':
|
||||
xml = dataframe_to_xml(array, name, roffset, coffset, rows, cols, format)
|
||||
else:
|
||||
raise VariableError("Do not know how to convert type %s to table" % (type_name))
|
||||
|
||||
return "<xml>%s</xml>" % xml
|
||||
|
||||
|
||||
def array_to_xml(array, roffset, coffset, rows, cols, format):
|
||||
xml = ""
|
||||
rows = min(rows, MAXIMUM_ARRAY_SIZE)
|
||||
cols = min(cols, MAXIMUM_ARRAY_SIZE)
|
||||
|
||||
|
||||
#there is no obvious rule for slicing (at least 5 choices)
|
||||
# there is no obvious rule for slicing (at least 5 choices)
|
||||
if len(array) == 1 and (rows > 1 or cols > 1):
|
||||
array = array[0]
|
||||
if array.size > len(array):
|
||||
@@ -489,11 +518,11 @@ def array_to_meta_xml(array, name, format):
|
||||
elif l == 2:
|
||||
rows = min(array.shape[-2], MAX_SLICE_SIZE)
|
||||
cols = min(array.shape[-1], MAX_SLICE_SIZE)
|
||||
if cols < array.shape[-1] or rows < array.shape[-2]:
|
||||
if cols < array.shape[-1] or rows < array.shape[-2]:
|
||||
reslice = '[0:%s, 0:%s]' % (rows, cols)
|
||||
array = array[0:rows, 0:cols]
|
||||
|
||||
#avoid slice duplication
|
||||
# avoid slice duplication
|
||||
if not slice.endswith(reslice):
|
||||
slice += reslice
|
||||
|
||||
@@ -501,8 +530,86 @@ def array_to_meta_xml(array, name, format):
|
||||
if type in "biufc":
|
||||
bounds = (array.min(), array.max())
|
||||
xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"%s\" type=\"%s\" max=\"%s\" min=\"%s\"/>' % \
|
||||
(slice, rows, cols, format, type, bounds[1], bounds[0])
|
||||
(slice, rows, cols, format, type, bounds[1], bounds[0])
|
||||
return array, xml, rows, cols, format
|
||||
|
||||
|
||||
def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
|
||||
"""
|
||||
:type df: pandas.core.frame.DataFrame
|
||||
:type name: str
|
||||
:type coffset: int
|
||||
:type roffset: int
|
||||
:type rows: int
|
||||
:type cols: int
|
||||
:type format: str
|
||||
|
||||
|
||||
"""
|
||||
num_rows = min(df.shape[0], MAX_SLICE_SIZE)
|
||||
num_cols = min(df.shape[1], MAX_SLICE_SIZE)
|
||||
if (num_rows, num_cols) != df.shape:
|
||||
df = df.iloc[0:num_rows, 0: num_cols]
|
||||
slice = '.iloc[0:%s, 0:%s]' % (num_rows, num_cols)
|
||||
else:
|
||||
slice = ''
|
||||
slice = name + slice
|
||||
xml = '<array slice=\"%s\" rows=\"%s\" cols=\"%s\" format=\"\" type=\"\" max=\"0\" min=\"0\"/>\n' % \
|
||||
(slice, num_rows, num_cols)
|
||||
|
||||
if (rows, cols) == (-1, -1):
|
||||
rows, cols = num_rows, num_cols
|
||||
|
||||
rows = min(rows, MAXIMUM_ARRAY_SIZE)
|
||||
cols = min(min(cols, MAXIMUM_ARRAY_SIZE), num_cols)
|
||||
# need to precompute column bounds here before slicing!
|
||||
col_bounds = [None] * cols
|
||||
for col in range(cols):
|
||||
dtype = df.dtypes.iloc[col].kind
|
||||
if dtype in "biufc":
|
||||
cvalues = df.iloc[:, col]
|
||||
bounds = (cvalues.min(), cvalues.max())
|
||||
else:
|
||||
bounds = (0, 0)
|
||||
col_bounds[col] = bounds
|
||||
|
||||
df = df.iloc[roffset: roffset + rows, coffset: coffset + cols]
|
||||
rows, cols = df.shape
|
||||
|
||||
def default_format(type):
|
||||
if type == 'f':
|
||||
return '.5f'
|
||||
elif type == 'i' or type == 'u':
|
||||
return 'd'
|
||||
else:
|
||||
return 's'
|
||||
|
||||
xml += "<headerdata rows=\"%s\" cols=\"%s\">\n" % (rows, cols)
|
||||
format = format.replace('%', '')
|
||||
col_formats = []
|
||||
for col in range(cols):
|
||||
label = df.axes[1].values[col]
|
||||
if isinstance(label, tuple):
|
||||
label = '/'.join(label)
|
||||
label = str(label)
|
||||
dtype = df.dtypes.iloc[col].kind
|
||||
fmt = format if (dtype == 'f' and format) else default_format(dtype)
|
||||
col_formats.append('%' + fmt)
|
||||
bounds = col_bounds[col]
|
||||
|
||||
xml += '<colheader index=\"%s\" label=\"%s\" type=\"%s\" format=\"%s\" max=\"%s\" min=\"%s\" />\n' % \
|
||||
(str(col), label, dtype, fmt, bounds[1], bounds[0])
|
||||
for row, label in enumerate(iter(df.axes[0])):
|
||||
if isinstance(label, tuple):
|
||||
label = '/'.join(label)
|
||||
xml += "<rowheader index=\"%s\" label = \"%s\"/>\n" % \
|
||||
(str(row), label)
|
||||
xml += "</headerdata>\n"
|
||||
xml += "<arraydata rows=\"%s\" cols=\"%s\"/>\n" % (rows, cols)
|
||||
for row in range(rows):
|
||||
xml += "<row index=\"%s\"/>\n" % str(row)
|
||||
for col in range(cols):
|
||||
value = df.iat[row, col]
|
||||
value = col_formats[col] % value
|
||||
xml += var_to_xml(value, '')
|
||||
return xml
|
||||
|
||||
@@ -18,6 +18,8 @@ package com.jetbrains.python.debugger;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
@@ -31,6 +33,10 @@ public class ArrayChunk {
|
||||
private final String myFormat;
|
||||
private final String myType;
|
||||
private final Object[][] myData;
|
||||
private final List<String> myRowLabels;
|
||||
private final List<ColHeader> myColHeaders;
|
||||
|
||||
|
||||
|
||||
public ArrayChunk(@NotNull PyDebugValue value,
|
||||
String slicePresentation,
|
||||
@@ -40,7 +46,7 @@ public class ArrayChunk {
|
||||
String min,
|
||||
String format,
|
||||
String type,
|
||||
@Nullable Object[][] data) {
|
||||
@Nullable Object[][] data, List<String> labels, List<ColHeader> headers) {
|
||||
myValue = value;
|
||||
mySlicePresentation = slicePresentation;
|
||||
myRows = rows;
|
||||
@@ -50,6 +56,8 @@ public class ArrayChunk {
|
||||
myFormat = format;
|
||||
myType = type;
|
||||
myData = data;
|
||||
myRowLabels = labels;
|
||||
myColHeaders = headers;
|
||||
}
|
||||
|
||||
public PyDebugValue getValue() {
|
||||
@@ -87,4 +95,50 @@ public class ArrayChunk {
|
||||
public Object[][] getData() {
|
||||
return myData;
|
||||
}
|
||||
|
||||
public List<String> getRowLabels() {
|
||||
return myRowLabels;
|
||||
}
|
||||
|
||||
public List<ColHeader> getColHeaders() {
|
||||
return myColHeaders;
|
||||
}
|
||||
|
||||
public static class ColHeader
|
||||
{
|
||||
private final String myLabel;
|
||||
private final String myType;
|
||||
private final String myFormat;
|
||||
private final String myMax;
|
||||
private final String myMin;
|
||||
|
||||
|
||||
public ColHeader(String label, String type, String format, String max, String min) {
|
||||
myLabel = label;
|
||||
myType = type;
|
||||
myFormat = format;
|
||||
myMax = max;
|
||||
myMin = min;
|
||||
}
|
||||
|
||||
public String getLabel() {
|
||||
return myLabel;
|
||||
}
|
||||
|
||||
public String getType() {
|
||||
return myType;
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return myFormat;
|
||||
}
|
||||
|
||||
public String getMax() {
|
||||
return myMax;
|
||||
}
|
||||
|
||||
public String getMin() {
|
||||
return myMin;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,89 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
public class ArrayChunkBuilder {
|
||||
private PyDebugValue myValue;
|
||||
private String myPresentation;
|
||||
private int myRows;
|
||||
private int myColumns;
|
||||
private String myMax;
|
||||
private String myMin;
|
||||
private String myFormat;
|
||||
private String myType;
|
||||
private Object[][] myData = null;
|
||||
private List<String> myRowLabels = null;
|
||||
private List<ArrayChunk.ColHeader> myColHeaders = null;
|
||||
|
||||
public ArrayChunkBuilder setValue(PyDebugValue value) {
|
||||
myValue = value;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setSlicePresentation(String presentation) {
|
||||
myPresentation = presentation;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setRows(int rows) {
|
||||
myRows = rows;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setColumns(int columns) {
|
||||
myColumns = columns;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setMax(String max) {
|
||||
myMax = max;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setMin(String min) {
|
||||
myMin = min;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setFormat(String format) {
|
||||
myFormat = format;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setType(String type) {
|
||||
myType = type;
|
||||
return this;
|
||||
}
|
||||
|
||||
public ArrayChunkBuilder setData(Object[][] data) {
|
||||
myData = data;
|
||||
return this;
|
||||
}
|
||||
|
||||
public void setRowLabels(List<String> rowLabels) {
|
||||
myRowLabels = rowLabels;
|
||||
}
|
||||
|
||||
public void setColHeaders(List<ArrayChunk.ColHeader> colHeaders) {
|
||||
myColHeaders = colHeaders;
|
||||
}
|
||||
|
||||
public ArrayChunk createArrayChunk() {
|
||||
return new ArrayChunk(myValue, myPresentation, myRows, myColumns, myMax, myMin, myFormat, myType, myData, myRowLabels, myColHeaders);
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.jetbrains.python.debugger.pydev;
|
||||
|
||||
import com.google.common.collect.Lists;
|
||||
import com.intellij.openapi.util.text.StringUtil;
|
||||
import com.jetbrains.python.debugger.*;
|
||||
import com.thoughtworks.xstream.io.naming.NoNameCoder;
|
||||
@@ -33,13 +34,14 @@ public class ProtocolParser {
|
||||
reader.moveDown();
|
||||
if ("arg".equals(reader.getNodeName())) {
|
||||
signature.addArgument(readString(reader, "name", ""), readString(reader, "type", ""));
|
||||
} else
|
||||
if ("return".equals(reader.getNodeName())) {
|
||||
}
|
||||
else if ("return".equals(reader.getNodeName())) {
|
||||
signature.addReturnType(readString(reader, "type", ""));
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new PyDebuggerException("Expected <arg> or <return>, found " + reader.getNodeName());
|
||||
}
|
||||
|
||||
|
||||
reader.moveUp();
|
||||
}
|
||||
|
||||
@@ -54,9 +56,11 @@ public class ProtocolParser {
|
||||
boolean isAsyncio;
|
||||
if (eventName.equals("threading_event")) {
|
||||
isAsyncio = false;
|
||||
} else if (eventName.equals("asyncio_event")) {
|
||||
}
|
||||
else if (eventName.equals("asyncio_event")) {
|
||||
isAsyncio = true;
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
throw new PyDebuggerException("Expected <threading_event> or <asyncio_event>, found " + reader.getNodeName());
|
||||
}
|
||||
|
||||
@@ -73,7 +77,8 @@ public class ProtocolParser {
|
||||
String parentThread = readString(reader, "parent", "");
|
||||
if (!parentThread.isEmpty()) {
|
||||
threadingEvent = new PyThreadEvent(time, thread_id, name, parentThread, isAsyncio);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
threadingEvent = new PyThreadEvent(time, thread_id, name, isAsyncio);
|
||||
}
|
||||
}
|
||||
@@ -269,28 +274,57 @@ public class ProtocolParser {
|
||||
|
||||
public static ArrayChunk parseArrayValues(final String text, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
|
||||
final XppReader reader = openReader(text, false);
|
||||
ArrayChunk result = null;
|
||||
ArrayChunkBuilder result = new ArrayChunkBuilder();
|
||||
if (reader.hasMoreChildren()) {
|
||||
reader.moveDown();
|
||||
if (!"array".equals(reader.getNodeName())) {
|
||||
throw new PyDebuggerException("Expected <array> at first node, found " + reader.getNodeName());
|
||||
}
|
||||
String slice = readString(reader, "slice", null);
|
||||
int rows = readInt(reader, "rows", null);
|
||||
int cols = readInt(reader, "cols", null);
|
||||
String format = "%" + readString(reader, "format", null);
|
||||
String type = readString(reader, "type", null);
|
||||
String max = readString(reader, "max", null);
|
||||
String min = readString(reader, "min", null);
|
||||
result =
|
||||
new ArrayChunk(new PyDebugValue(slice, null, null, null, false, false, false, frameAccessor), slice, rows, cols, max, min, format,
|
||||
type, null);
|
||||
result.setSlicePresentation(slice);
|
||||
result.setRows(readInt(reader, "rows", null));
|
||||
result.setColumns(readInt(reader, "cols", null));
|
||||
result.setFormat("%" + readString(reader, "format", null));
|
||||
result.setType(readString(reader, "type", null));
|
||||
result.setMax(readString(reader, "max", null));
|
||||
result.setMin(readString(reader, "min", null));
|
||||
result.setValue(new PyDebugValue(slice, null, null, null, false, false, false, frameAccessor));
|
||||
reader.moveUp();
|
||||
}
|
||||
if ("headerdata".equals(reader.peekNextChild())) {
|
||||
parseArrayHeaderData(reader, result);
|
||||
}
|
||||
|
||||
Object[][] data = parseArrayValues(reader, frameAccessor);
|
||||
return new ArrayChunk(result.getValue(), result.getSlicePresentation(), result.getRows(), result.getColumns(), result.getMax(),
|
||||
result.getMin(), result.getFormat(), result.getType(), data);
|
||||
result.setData(data);
|
||||
return result.createArrayChunk();
|
||||
}
|
||||
|
||||
private static void parseArrayHeaderData(XppReader reader, ArrayChunkBuilder result) throws PyDebuggerException {
|
||||
List<String> rowHeaders = Lists.newArrayList();
|
||||
List<ArrayChunk.ColHeader> colHeaders = Lists.newArrayList();
|
||||
reader.moveDown();
|
||||
while (reader.hasMoreChildren()) {
|
||||
reader.moveDown();
|
||||
if ("colheader".equals(reader.getNodeName())) {
|
||||
colHeaders.add(new ArrayChunk.ColHeader(
|
||||
readString(reader, "label", null),
|
||||
readString(reader, "type", null),
|
||||
readString(reader, "format", null),
|
||||
readString(reader, "max", null),
|
||||
readString(reader, "min", null)));
|
||||
}
|
||||
else if ("rowheader".equals(reader.getNodeName())) {
|
||||
rowHeaders.add(readString(reader, "label", null));
|
||||
}
|
||||
else {
|
||||
throw new PyDebuggerException("Invalid node name" + reader.getNodeName());
|
||||
}
|
||||
reader.moveUp();
|
||||
}
|
||||
result.setColHeaders(colHeaders);
|
||||
result.setRowLabels(rowHeaders);
|
||||
reader.moveUp();
|
||||
}
|
||||
|
||||
public static Object[][] parseArrayValues(final XppReader reader, final PyFrameAccessor frameAccessor) throws PyDebuggerException {
|
||||
|
||||
@@ -794,7 +794,7 @@
|
||||
<add-to-group group-id="Internal"/>
|
||||
</action>
|
||||
|
||||
<action id="PyDebugger.ViewArray" class="com.jetbrains.python.debugger.array.PyViewArrayAction" text="View as Array">
|
||||
<action id="PyDebugger.ViewArray" class="com.jetbrains.python.debugger.containerview.PyViewNumericContainerAction" text="View as Array">
|
||||
<add-to-group group-id="XDebugger.ValueGroup" anchor="after" relative-to-action="Debugger.Tree.AddToWatches"/>
|
||||
</action>
|
||||
|
||||
|
||||
@@ -17,6 +17,8 @@ package com.jetbrains.python.debugger.array;
|
||||
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.PyNumericViewUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
@@ -26,14 +28,14 @@ import java.awt.*;
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
class ArrayTableCellRenderer extends DefaultTableCellRenderer {
|
||||
class ArrayTableCellRenderer extends DefaultTableCellRenderer implements ColoredCellRenderer {
|
||||
|
||||
private double myMin = Double.MIN_VALUE;
|
||||
private double myMax = Double.MIN_VALUE;
|
||||
private String myComplexMin;
|
||||
private String myComplexMax;
|
||||
private boolean myColored = true;
|
||||
private String myType;
|
||||
private final String myType;
|
||||
|
||||
public ArrayTableCellRenderer(double min, double max, String type) {
|
||||
setHorizontalAlignment(CENTER);
|
||||
@@ -62,9 +64,8 @@ class ArrayTableCellRenderer extends DefaultTableCellRenderer {
|
||||
if (myMax != myMin) {
|
||||
if (myColored && value != null) {
|
||||
try {
|
||||
double rangedValue = NumpyArrayTable.getRangedValue(value.toString(), myType, myMin, myMax, myComplexMax, myComplexMin);
|
||||
//noinspection UseJBColor
|
||||
this.setBackground(new Color((int)Math.round(255 * rangedValue), 0, (int)Math.round(255 * (1 - rangedValue)), 130));
|
||||
double rangedValue = PyNumericViewUtil.getRangedValue(value.toString(), myType, myMin, myMax, myComplexMax, myComplexMin);
|
||||
this.setBackground(PyNumericViewUtil.rangedValueToColor(rangedValue));
|
||||
}
|
||||
catch (NumberFormatException ignored) {
|
||||
}
|
||||
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright 2000-2014 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.jetbrains.python.debugger.array;
|
||||
|
||||
import com.intellij.openapi.editor.Editor;
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.DefaultTableModel;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyListener;
|
||||
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
public class ArrayTableForm {
|
||||
private EditorTextField mySliceTextField;
|
||||
private JCheckBox myColoredCheckbox;
|
||||
private EditorTextField myFormatTextField;
|
||||
private JBScrollPane myScrollPane;
|
||||
private JLabel myFormatLabel;
|
||||
private JPanel myFormatPanel;
|
||||
private JPanel myMainPanel;
|
||||
private JTable myTable;
|
||||
private final Project myProject;
|
||||
private KeyListener myResliceCallback;
|
||||
private KeyListener myReformatCallback;
|
||||
|
||||
|
||||
private static final String DATA_LOADING_IN_PROCESS = "Please wait, load array data.";
|
||||
private static final String NOT_APPLICABLE = "View not applicable for ";
|
||||
|
||||
public ArrayTableForm(@NotNull Project project, KeyListener resliceCallback, KeyListener reformatCallback) {
|
||||
myProject = project;
|
||||
myResliceCallback = resliceCallback;
|
||||
myReformatCallback = reformatCallback;
|
||||
}
|
||||
|
||||
private void createUIComponents() {
|
||||
mySliceTextField = new EditorTextField("", myProject, PythonFileType.INSTANCE) {
|
||||
@Override
|
||||
protected EditorEx createEditor() {
|
||||
EditorEx editor = super.createEditor();
|
||||
editor.getContentComponent().addKeyListener(myResliceCallback);
|
||||
return editor;
|
||||
}
|
||||
};
|
||||
|
||||
myTable = new JBTableWithRowHeaders();
|
||||
|
||||
myScrollPane = ((JBTableWithRowHeaders)myTable).getScrollPane();
|
||||
|
||||
myFormatTextField = new EditorTextField("", myProject, PythonFileType.INSTANCE) {
|
||||
@Override
|
||||
protected EditorEx createEditor() {
|
||||
EditorEx editor = super.createEditor();
|
||||
editor.getContentComponent().addKeyListener(myReformatCallback);
|
||||
return editor;
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public EditorTextField getSliceTextField() {
|
||||
return mySliceTextField;
|
||||
}
|
||||
|
||||
public EditorTextField getFormatTextField() {
|
||||
return myFormatTextField;
|
||||
}
|
||||
|
||||
public JTable getTable() {
|
||||
return myTable;
|
||||
}
|
||||
|
||||
public JCheckBox getColoredCheckbox() {
|
||||
return myColoredCheckbox;
|
||||
}
|
||||
|
||||
//public void setDefaultStatus() {
|
||||
// if (myTable != null) {
|
||||
//myTable.getEmptyText().setText(DATA_LOADING_IN_PROCESS);
|
||||
//}
|
||||
//}
|
||||
|
||||
//public void setNotApplicableStatus(PyDebugValue node) {
|
||||
// myTable.getEmptyText().setText(NOT_APPLICABLE + node.getName());
|
||||
//}
|
||||
|
||||
public JComponent getMainPanel() {
|
||||
return myMainPanel;
|
||||
}
|
||||
|
||||
public JBScrollPane getScrollPane() {
|
||||
return myScrollPane;
|
||||
}
|
||||
|
||||
public static class ColumnHeaderRenderer extends DefaultTableHeaderCellRenderer {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
|
||||
int selectedColumn = table.getSelectedColumn();
|
||||
if (selectedColumn == column) {
|
||||
setFont(getFont().deriveFont(Font.BOLD));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultTableHeaderCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
public DefaultTableHeaderCellRenderer() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
setHorizontalTextPosition(LEFT);
|
||||
setVerticalAlignment(BOTTOM);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value,
|
||||
isSelected, hasFocus, row, column);
|
||||
JTableHeader tableHeader = table.getTableHeader();
|
||||
if (tableHeader != null) {
|
||||
setForeground(tableHeader.getForeground());
|
||||
}
|
||||
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -24,9 +24,11 @@ import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.ConcurrencyUtil;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.ArrayChunkBuilder;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import java.util.concurrent.*;
|
||||
|
||||
/**
|
||||
@@ -39,7 +41,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
|
||||
private int myRows;
|
||||
private int myColumns;
|
||||
private final NumpyArrayTable myProvider;
|
||||
private final TableChunkDatasource myProvider;
|
||||
|
||||
|
||||
private final ExecutorService myExecutorService = ConcurrencyUtil.newSingleThreadExecutor("Python async table");
|
||||
@@ -48,16 +50,14 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
private LoadingCache<Pair<Integer, Integer>, ListenableFuture<ArrayChunk>> myChunkCache = CacheBuilder.newBuilder().build(
|
||||
new CacheLoader<Pair<Integer, Integer>, ListenableFuture<ArrayChunk>>() {
|
||||
@Override
|
||||
public ListenableFuture<ArrayChunk> load(final Pair<Integer, Integer> key) throws Exception {
|
||||
final PyDebugValue value = myProvider.getDebugValue();
|
||||
final PyDebugValue slicedValue =
|
||||
new PyDebugValue(myProvider.getSliceText(), value.getType(), value.getTypeQualifier(), value.getValue(), value.isContainer(),
|
||||
value.isReturnedVal(), value.isErrorOnEval(), value.getParent(), value.getFrameAccessor());
|
||||
public ListenableFuture<ArrayChunk> load(@NotNull final Pair<Integer, Integer> key) throws Exception {
|
||||
|
||||
ListenableFutureTask<ArrayChunk> task = ListenableFutureTask.create(() -> value.getFrameAccessor()
|
||||
.getArrayItems(slicedValue, key.first, key.second, Math.min(CHUNK_ROW_SIZE, getRowCount() - key.first),
|
||||
Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second),
|
||||
myProvider.getFormat()));
|
||||
ListenableFutureTask<ArrayChunk> task = ListenableFutureTask.create(() -> {
|
||||
ArrayChunk chunk = myProvider.getChunk(key.first, key.second, Math.min(CHUNK_ROW_SIZE, getRowCount() - key.first),
|
||||
Math.min(CHUNK_COL_SIZE, getColumnCount() - key.second));
|
||||
handleChunkAdded(key.first, key.second, chunk);
|
||||
return chunk;
|
||||
});
|
||||
|
||||
myExecutorService.execute(task);
|
||||
|
||||
@@ -65,7 +65,7 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
}
|
||||
});
|
||||
|
||||
public AsyncArrayTableModel(int rows, int columns, NumpyArrayTable provider) {
|
||||
public AsyncArrayTableModel(int rows, int columns, TableChunkDatasource provider) {
|
||||
myRows = rows;
|
||||
myColumns = columns;
|
||||
myProvider = provider;
|
||||
@@ -73,14 +73,6 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int col) {
|
||||
//Pair<Integer, Integer> key = itemToChunkKey(row, col);
|
||||
//try {
|
||||
// return myChunkCache.get(key).isDone();
|
||||
//}
|
||||
//catch (ExecutionException e) {
|
||||
// return false;
|
||||
//}
|
||||
//TODO: make it editable
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -160,13 +152,11 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
Pair<Integer, Integer> key = itemToChunkKey(roffset * CHUNK_ROW_SIZE, coffset * CHUNK_COL_SIZE);
|
||||
final Object[][] chunkData = new Object[CHUNK_ROW_SIZE][CHUNK_COL_SIZE];
|
||||
for (int r = 0; r < CHUNK_ROW_SIZE; r++) {
|
||||
for (int c = 0; c < CHUNK_COL_SIZE; c++) {
|
||||
chunkData[r][c] = data[roffset * CHUNK_ROW_SIZE + r][coffset * CHUNK_COL_SIZE + c];
|
||||
}
|
||||
System.arraycopy(data[roffset * CHUNK_ROW_SIZE + r], coffset * 30, chunkData[r], 0, CHUNK_COL_SIZE);
|
||||
}
|
||||
myChunkCache.put(key, new ListenableFuture<ArrayChunk>() {
|
||||
@Override
|
||||
public void addListener(Runnable listener, Executor executor) {
|
||||
public void addListener(@NotNull Runnable listener, @NotNull Executor executor) {
|
||||
|
||||
}
|
||||
|
||||
@@ -187,15 +177,53 @@ public class AsyncArrayTableModel extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public ArrayChunk get() throws InterruptedException, ExecutionException {
|
||||
return new ArrayChunk(chunk.getValue(), null, 0, 0, null, null, null, null, chunkData);
|
||||
return new ArrayChunkBuilder().setValue(chunk.getValue()).setSlicePresentation(null).setRows(0).setColumns(0).setMax(null)
|
||||
.setMin(null).setFormat(null).setType(null).setData(chunkData).createArrayChunk();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayChunk get(long timeout, TimeUnit unit) throws InterruptedException, ExecutionException, TimeoutException {
|
||||
return new ArrayChunk(chunk.getValue(), null, 0, 0, null, null, null, null, chunkData);
|
||||
return new ArrayChunkBuilder().setValue(chunk.getValue()).setSlicePresentation(null).setRows(0).setColumns(0).setMax(null)
|
||||
.setMin(null).setFormat(null).setType(null).setData(chunkData).createArrayChunk();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
handleChunkAdded(0, 0, chunk);
|
||||
}
|
||||
|
||||
protected void handleChunkAdded(Integer rowOffset, Integer colOffset, ArrayChunk chunk) {
|
||||
|
||||
}
|
||||
|
||||
public TableModel getRowHeaderModel() {
|
||||
return new RowNumberHeaderModel();
|
||||
}
|
||||
|
||||
|
||||
private class RowNumberHeaderModel extends AbstractTableModel {
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return AsyncArrayTableModel.this.getRowCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
if (column == 0) {
|
||||
return " ";
|
||||
}
|
||||
throw new IllegalArgumentException("Table only has one column");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
return Integer.toString(rowIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -21,12 +21,10 @@ import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.event.ListSelectionEvent;
|
||||
import javax.swing.event.ListSelectionListener;
|
||||
import javax.swing.event.TableModelEvent;
|
||||
import javax.swing.event.TableModelListener;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import javax.swing.table.TableColumn;
|
||||
import javax.swing.table.TableModel;
|
||||
import java.awt.*;
|
||||
import java.awt.event.MouseListener;
|
||||
import java.beans.PropertyChangeEvent;
|
||||
@@ -39,59 +37,47 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
private final JBScrollPane myScrollPane;
|
||||
private RowHeaderTable myRowHeaderTable;
|
||||
|
||||
public JBScrollPane getScrollPane() {
|
||||
return myScrollPane;
|
||||
}
|
||||
|
||||
public JBTableWithRowHeaders() {
|
||||
setAutoResizeMode(JTable.AUTO_RESIZE_SUBSEQUENT_COLUMNS);
|
||||
setRowSelectionAllowed(false);
|
||||
setMaxItemsForSizeCalculation(50);
|
||||
setTableHeader(new CustomTableHeader(this));
|
||||
getTableHeader().setDefaultRenderer(new ArrayTableForm.ColumnHeaderRenderer());
|
||||
getTableHeader().setDefaultRenderer(new ColumnHeaderRenderer());
|
||||
getTableHeader().setReorderingAllowed(false);
|
||||
|
||||
myScrollPane = new JBScrollPane(this);
|
||||
JBTableWithRowHeaders.RowHeaderTable rowTable = new JBTableWithRowHeaders.RowHeaderTable(this);
|
||||
myScrollPane.setRowHeaderView(rowTable);
|
||||
myRowHeaderTable = new JBTableWithRowHeaders.RowHeaderTable(this);
|
||||
myScrollPane.setRowHeaderView(myRowHeaderTable);
|
||||
myScrollPane.setCorner(ScrollPaneConstants.UPPER_LEFT_CORNER,
|
||||
rowTable.getTableHeader());
|
||||
myRowHeaderTable.getTableHeader());
|
||||
}
|
||||
|
||||
setRowHeaderTable(rowTable);
|
||||
public JBScrollPane getScrollPane() {
|
||||
return myScrollPane;
|
||||
}
|
||||
|
||||
public boolean getScrollableTracksViewportWidth() {
|
||||
return getPreferredSize().width < getParent().getWidth();
|
||||
}
|
||||
|
||||
public RowHeaderTable getRowHeaderTable() {
|
||||
return myRowHeaderTable;
|
||||
}
|
||||
|
||||
public void setRowHeaderTable(RowHeaderTable rowHeaderTable) {
|
||||
myRowHeaderTable = rowHeaderTable;
|
||||
@Override
|
||||
public void setModel(@NotNull TableModel model) {
|
||||
|
||||
super.setModel(model);
|
||||
if (model instanceof AsyncArrayTableModel) {
|
||||
myRowHeaderTable.setModel(((AsyncArrayTableModel)model).getRowHeaderModel());
|
||||
}
|
||||
}
|
||||
|
||||
public static class RowHeaderTable extends JBTable implements PropertyChangeListener, TableModelListener {
|
||||
private JTable myMainTable;
|
||||
private int myRowShift = 0;
|
||||
|
||||
public RowHeaderTable(JTable table) {
|
||||
myMainTable = table;
|
||||
myMainTable.getModel().addTableModelListener(this);
|
||||
|
||||
setFocusable(false);
|
||||
setAutoCreateColumnsFromModel(false);
|
||||
setSelectionModel(myMainTable.getSelectionModel());
|
||||
setSelectionMode(ListSelectionModel.SINGLE_SELECTION);
|
||||
|
||||
TableColumn column = new TableColumn();
|
||||
column.setHeaderValue(" ");
|
||||
addColumn(column);
|
||||
column.setCellRenderer(new RowNumberRenderer());
|
||||
|
||||
getColumnModel().getColumn(0).setPreferredWidth(50);
|
||||
setPreferredScrollableViewportSize(getPreferredSize());
|
||||
setRowHeight(myMainTable.getRowHeight());
|
||||
MouseListener[] listeners = getMouseListeners();
|
||||
for (MouseListener l : listeners) {
|
||||
@@ -105,9 +91,16 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
super.paintComponent(g);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return myMainTable.getRowCount();
|
||||
public void setModel(@NotNull TableModel model) {
|
||||
setAutoCreateColumnsFromModel(true);
|
||||
super.setModel(model);
|
||||
if (getColumnModel().getColumnCount() > 0) {
|
||||
getColumnModel().getColumn(0).setPreferredWidth(50);
|
||||
getColumnModel().getColumn(0).setCellRenderer(new RowNumberRenderer());
|
||||
setPreferredScrollableViewportSize(getPreferredSize());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -116,14 +109,6 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
return super.getRowHeight(row);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int row, int column) {
|
||||
return Integer.toString(row + myRowShift);
|
||||
}
|
||||
|
||||
public void setRowShift(int shift) {
|
||||
myRowShift = shift;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isCellEditable(int row, int column) {
|
||||
@@ -131,10 +116,6 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void setValueAt(Object value, int row, int column) {
|
||||
}
|
||||
|
||||
public void propertyChange(PropertyChangeEvent e) {
|
||||
if ("selectionModel".equals(e.getPropertyName())) {
|
||||
setSelectionModel(myMainTable.getSelectionModel());
|
||||
@@ -145,17 +126,12 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
}
|
||||
|
||||
if ("model".equals(e.getPropertyName())) {
|
||||
myMainTable.getModel().addTableModelListener(this);
|
||||
revalidate();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void tableChanged(TableModelEvent e) {
|
||||
revalidate();
|
||||
}
|
||||
|
||||
private class RowNumberRenderer extends DefaultTableCellRenderer {
|
||||
private static class RowNumberRenderer extends DefaultTableCellRenderer {
|
||||
public RowNumberRenderer() {
|
||||
setHorizontalAlignment(SwingConstants.CENTER);
|
||||
}
|
||||
@@ -189,12 +165,7 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
public CustomTableHeader(JTable table) {
|
||||
super();
|
||||
setColumnModel(table.getColumnModel());
|
||||
table.getColumnModel().getSelectionModel().addListSelectionListener(new ListSelectionListener() {
|
||||
@Override
|
||||
public void valueChanged(ListSelectionEvent e) {
|
||||
repaint();
|
||||
}
|
||||
});
|
||||
table.getColumnModel().getSelectionModel().addListSelectionListener(e -> repaint());
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -202,4 +173,39 @@ public class JBTableWithRowHeaders extends JBTable {
|
||||
repaint();
|
||||
}
|
||||
}
|
||||
|
||||
public static class ColumnHeaderRenderer extends DefaultTableHeaderCellRenderer {
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value, boolean selected, boolean focused, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value, selected, focused, row, column);
|
||||
int selectedColumn = table.getSelectedColumn();
|
||||
if (selectedColumn == column) {
|
||||
setFont(getFont().deriveFont(Font.BOLD));
|
||||
}
|
||||
return this;
|
||||
}
|
||||
}
|
||||
|
||||
public static class DefaultTableHeaderCellRenderer extends DefaultTableCellRenderer {
|
||||
|
||||
public DefaultTableHeaderCellRenderer() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
setHorizontalTextPosition(LEFT);
|
||||
setVerticalAlignment(BOTTOM);
|
||||
setOpaque(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int column) {
|
||||
super.getTableCellRendererComponent(table, value,
|
||||
isSelected, hasFocus, row, column);
|
||||
JTableHeader tableHeader = table.getTableHeader();
|
||||
if (tableHeader != null) {
|
||||
setForeground(tableHeader.getForeground());
|
||||
}
|
||||
setBorder(UIManager.getBorder("TableHeader.cellBorder"));
|
||||
return this;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -15,211 +15,52 @@
|
||||
*/
|
||||
package com.jetbrains.python.debugger.array;
|
||||
|
||||
import com.intellij.codeInsight.hint.HintManager;
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.*;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.NumericContainerViewTable;
|
||||
import com.jetbrains.python.debugger.containerview.ViewNumericContainerDialog;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
public class NumpyArrayTable {
|
||||
private final PyDebugValue myValue;
|
||||
private final PyViewArrayAction.ViewArrayDialog myDialog;
|
||||
private final ArrayTableForm myComponent;
|
||||
private final JTable myTable;
|
||||
private Project myProject;
|
||||
private PyDebuggerEvaluator myEvaluator;
|
||||
private String myDtypeKind;
|
||||
private ArrayTableCellRenderer myTableCellRenderer;
|
||||
private AsyncArrayTableModel myPagingModel;
|
||||
public final class NumpyArrayTable extends NumericContainerViewTable {
|
||||
|
||||
private final static int COLUMNS_IN_DEFAULT_SLICE = 40;
|
||||
private final static int ROWS_IN_DEFAULT_SLICE = 40;
|
||||
|
||||
private final static int COLUMNS_IN_DEFAULT_VIEW = 1000;
|
||||
private final static int ROWS_IN_DEFAULT_VIEW = 1000;
|
||||
|
||||
private static final Pattern PY_COMPLEX_NUMBER = Pattern.compile("([+-]?[.\\d^j]*)([+-]?[e.\\d]*j)?");
|
||||
|
||||
private final static int HUGE_ARRAY_SIZE = 1000 * 1000;
|
||||
private final static String LOAD_SMALLER_SLICE = "Full slice too large and would slow down debugger, shrunk to smaller slice.";
|
||||
private final static String DISABLE_COLOR_FOR_HUGE_ARRAY =
|
||||
"Disable color because array too big and calculating min and max would slow down debugging.";
|
||||
|
||||
private static final Logger LOG = Logger.getInstance("#com.jetbrains.python.debugger.array.NumpyArrayValueProvider");
|
||||
private ArrayTableCellRenderer myArrayTableCellRenderer;
|
||||
|
||||
public NumpyArrayTable(@NotNull Project project,
|
||||
@NotNull PyViewArrayAction.ViewArrayDialog dialog, @NotNull PyDebugValue value) {
|
||||
myValue = value;
|
||||
myDialog = dialog;
|
||||
myComponent = new ArrayTableForm(project, new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
doReslice();
|
||||
}
|
||||
}
|
||||
}, new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
doApplyFormat();
|
||||
}
|
||||
}
|
||||
});
|
||||
myTable = myComponent.getTable();
|
||||
myProject = project;
|
||||
myEvaluator = new PyDebuggerEvaluator(project, getDebugValue().getFrameAccessor());
|
||||
@NotNull ViewNumericContainerDialog dialog, @NotNull PyDebugValue value) {
|
||||
super(project, dialog, value);
|
||||
}
|
||||
|
||||
public ArrayTableForm getComponent() {
|
||||
return myComponent;
|
||||
@Override
|
||||
protected AsyncArrayTableModel createTableModel(int rowCount, int columnCount) {
|
||||
return new AsyncArrayTableModel(rowCount, columnCount, this);
|
||||
}
|
||||
|
||||
private void initComponent() {
|
||||
myComponent.getColoredCheckbox().setEnabled(false);
|
||||
myComponent.getColoredCheckbox().addItemListener(new ItemListener() {
|
||||
@Override
|
||||
public void itemStateChanged(ItemEvent e) {
|
||||
if (e.getSource() == myComponent.getColoredCheckbox()) {
|
||||
|
||||
if (myTable.getRowCount() > 0 &&
|
||||
myTable.getColumnCount() > 0 &&
|
||||
myTable.getCellRenderer(0, 0) instanceof ArrayTableCellRenderer) {
|
||||
ArrayTableCellRenderer renderer = (ArrayTableCellRenderer)myTable.getCellRenderer(0, 0);
|
||||
if (myComponent.getColoredCheckbox().isSelected()) {
|
||||
renderer.setColored(true);
|
||||
}
|
||||
else {
|
||||
renderer.setColored(false);
|
||||
}
|
||||
}
|
||||
myComponent.getScrollPane().repaint();
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
//make value name read-only
|
||||
myComponent.getSliceTextField().addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeFullName().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeFullName().length());
|
||||
if (block != null) {
|
||||
myComponent.getSliceTextField().getDocument().removeGuardedBlock(block);
|
||||
}
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected ColoredCellRenderer createCellRenderer(double minValue, double maxValue, ArrayChunk chunk) {
|
||||
myArrayTableCellRenderer = new ArrayTableCellRenderer(minValue, maxValue, chunk.getType());
|
||||
fillColorRange(chunk.getMin(), chunk.getMax());
|
||||
return myArrayTableCellRenderer;
|
||||
}
|
||||
|
||||
public void disableColor() {
|
||||
myTableCellRenderer.setMin(Double.MAX_VALUE);
|
||||
myTableCellRenderer.setMax(Double.MIN_VALUE);
|
||||
myTableCellRenderer.setColored(false);
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myComponent.getColoredCheckbox().setSelected(false);
|
||||
myComponent.getColoredCheckbox().setEnabled(false);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PyDebugValue getDebugValue() {
|
||||
return myValue;
|
||||
}
|
||||
|
||||
public PyFrameAccessor getEvaluator() {
|
||||
return myValue.getFrameAccessor();
|
||||
}
|
||||
|
||||
public void init() {
|
||||
init(getDebugValue().getEvaluationExpression(), false);
|
||||
}
|
||||
|
||||
public void init(final String slice, final boolean inPlace) {
|
||||
initComponent();
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
final PyDebugValue value = getDebugValue();
|
||||
PyDebugValue parent = value.getParent();
|
||||
final PyDebugValue slicedValue =
|
||||
new PyDebugValue(slice, value.getType(), null, value.getValue(), value.isContainer(), value.isReturnedVal(),
|
||||
value.isErrorOnEval(), parent, value.getFrameAccessor());
|
||||
|
||||
final String format = getFormat().isEmpty() ? "%" : getFormat();
|
||||
|
||||
try {
|
||||
initUi(value.getFrameAccessor()
|
||||
.getArrayItems(slicedValue, 0, 0, -1, -1, format), inPlace);
|
||||
}
|
||||
catch (PyDebuggerException e) {
|
||||
showError(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void initUi(@NotNull final ArrayChunk chunk, final boolean inPlace) {
|
||||
myPagingModel = new AsyncArrayTableModel(Math.min(chunk.getRows(), ROWS_IN_DEFAULT_VIEW),
|
||||
Math.min(chunk.getColumns(), COLUMNS_IN_DEFAULT_VIEW), this);
|
||||
myPagingModel.addToCache(chunk);
|
||||
myDtypeKind = chunk.getType();
|
||||
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myTable.setModel(myPagingModel);
|
||||
myComponent.getSliceTextField().setText(chunk.getSlicePresentation());
|
||||
myComponent.getFormatTextField().setText(chunk.getFormat());
|
||||
myDialog.setTitle(getTitlePresentation(chunk.getSlicePresentation()));
|
||||
myTableCellRenderer = new ArrayTableCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, chunk.getType());
|
||||
fillColorRange(chunk.getMin(), chunk.getMax());
|
||||
if (!isNumeric()) {
|
||||
disableColor();
|
||||
}
|
||||
else {
|
||||
myComponent.getColoredCheckbox().setEnabled(true);
|
||||
}
|
||||
|
||||
if (!inPlace) {
|
||||
myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0));
|
||||
JBTableWithRowHeaders.RowHeaderTable rowTable = ((JBTableWithRowHeaders)myTable).getRowHeaderTable();
|
||||
rowTable.setRowShift(0);
|
||||
}
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged();
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private static String getTitlePresentation(String slice) {
|
||||
@Override
|
||||
protected final String getTitlePresentation(String slice) {
|
||||
return "Array View: " + slice;
|
||||
}
|
||||
|
||||
|
||||
private void fillColorRange(String minValue, String maxValue) {
|
||||
double min;
|
||||
double max;
|
||||
if ("c".equals(myDtypeKind)) {
|
||||
min = 0;
|
||||
max = 1;
|
||||
myTableCellRenderer.setComplexMin(minValue);
|
||||
myTableCellRenderer.setComplexMax(maxValue);
|
||||
myArrayTableCellRenderer.setComplexMin(minValue);
|
||||
myArrayTableCellRenderer.setComplexMax(maxValue);
|
||||
}
|
||||
else if ("b".equals(myDtypeKind)) {
|
||||
min = minValue.equals("True") ? 1 : 0;
|
||||
@@ -230,145 +71,15 @@ public class NumpyArrayTable {
|
||||
max = Double.parseDouble(maxValue);
|
||||
}
|
||||
|
||||
myTableCellRenderer.setMin(min);
|
||||
myTableCellRenderer.setMax(max);
|
||||
}
|
||||
|
||||
public String getSliceText() {
|
||||
return myComponent.getSliceTextField().getText();
|
||||
myArrayTableCellRenderer.setMin(min);
|
||||
myArrayTableCellRenderer.setMax(max);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNumeric() {
|
||||
if (myDtypeKind != null) {
|
||||
return "biufc".contains(myDtypeKind.substring(0, 1));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
private void initTableModel(final boolean inPlace) {
|
||||
myPagingModel = new AsyncArrayTableModel(myPagingModel.getRowCount(), myPagingModel.getColumnCount(), this);
|
||||
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myTable.setModel(myPagingModel);
|
||||
if (!inPlace) {
|
||||
myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0));
|
||||
JBTableWithRowHeaders.RowHeaderTable rowTable = ((JBTableWithRowHeaders)myTable).getRowHeaderTable();
|
||||
rowTable.setRowShift(0);
|
||||
}
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged();
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public String correctStringValue(@NotNull Object value) {
|
||||
if (value instanceof String) {
|
||||
String corrected = (String)value;
|
||||
if (isNumeric()) {
|
||||
if (corrected.startsWith("\'") || corrected.startsWith("\"")) {
|
||||
corrected = corrected.substring(1, corrected.length() - 1);
|
||||
}
|
||||
}
|
||||
return corrected;
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
return Integer.toString((Integer)value);
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
public void setDtypeKind(String dtype) {
|
||||
this.myDtypeKind = dtype;
|
||||
}
|
||||
|
||||
public void showError(String message) {
|
||||
myDialog.setError(message);
|
||||
}
|
||||
|
||||
public void showInfoHint(final String message) {
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
if (myComponent.getSliceTextField().getEditor() != null) {
|
||||
HintManager.getInstance().showInformationHint(myComponent.getSliceTextField().getEditor(), message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void doReslice() {
|
||||
clearErrorMessage();
|
||||
init(getSliceText(), false);
|
||||
}
|
||||
|
||||
private void clearErrorMessage() {
|
||||
showError(null);
|
||||
}
|
||||
|
||||
private void doApplyFormat() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
clearErrorMessage();
|
||||
initTableModel(true);
|
||||
}
|
||||
|
||||
/**
|
||||
* @return double presentation from [0:1] range
|
||||
*/
|
||||
public static double getRangedValue(String value, String type, double min, double max, String complexMax, String complexMin) {
|
||||
if ("iuf".contains(type)) {
|
||||
return (Double.parseDouble(value) - min) / (max - min);
|
||||
}
|
||||
else if ("b".equals(type)) {
|
||||
return value.equals("True") ? 1 : 0;
|
||||
}
|
||||
else if ("c".equals(type)) {
|
||||
return getComplexRangedValue(value, complexMax, complexMin);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* type complex128 in numpy is compared by next rule:
|
||||
* A + Bj > C +Dj if A > C or A == C and B > D
|
||||
*/
|
||||
private static double getComplexRangedValue(String value, String complexMax, String complexMin) {
|
||||
Pair<Double, Double> med = parsePyComplex(value);
|
||||
Pair<Double, Double> max = parsePyComplex(complexMax);
|
||||
Pair<Double, Double> min = parsePyComplex(complexMin);
|
||||
double range = (med.first - min.first) / (max.first - min.first);
|
||||
if (max.first.equals(min.first)) {
|
||||
range = (med.second - min.second) / (max.second - min.second);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
private static Pair<Double, Double> parsePyComplex(@NotNull String pyComplexValue) {
|
||||
if (pyComplexValue.startsWith("(") && pyComplexValue.endsWith(")")) {
|
||||
pyComplexValue = pyComplexValue.substring(1, pyComplexValue.length() - 1);
|
||||
}
|
||||
Matcher matcher = PY_COMPLEX_NUMBER.matcher(pyComplexValue);
|
||||
if (matcher.matches()) {
|
||||
String real = matcher.group(1);
|
||||
String imag = matcher.group(2);
|
||||
if (real.contains("j") && imag == null) {
|
||||
return new Pair(new Double(0.0), Double.parseDouble(real.substring(0, real.length() - 1)));
|
||||
}
|
||||
else {
|
||||
return new Pair(Double.parseDouble(real), Double.parseDouble(imag.substring(0, imag.length() - 1)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Not a valid python complex value: " + pyComplexValue);
|
||||
}
|
||||
}
|
||||
|
||||
public String getNodeFullName() {
|
||||
return getDebugValue().getEvaluationExpression();
|
||||
}
|
||||
|
||||
public String getFormat() {
|
||||
return myComponent.getFormatTextField().getText();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,34 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.array;
|
||||
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebuggerException;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
/**
|
||||
* Created by Alla on 4/23/2016.
|
||||
*/
|
||||
public interface TableChunkDatasource {
|
||||
|
||||
String correctStringValue(@NotNull Object value);
|
||||
|
||||
void showError(String message);
|
||||
|
||||
|
||||
ArrayChunk getChunk(int rowOffset, int colOffset, int rows, int cols) throws PyDebuggerException;
|
||||
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import javax.swing.table.TableCellRenderer;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 5/10/2016.
|
||||
*/
|
||||
public interface ColoredCellRenderer extends TableCellRenderer {
|
||||
void setColored(boolean colored);
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.debugger.array.ArrayTableForm">
|
||||
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="2" column-count="5" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<form xmlns="http://www.intellij.com/uidesigner/form/" version="1" bind-to-class="com.jetbrains.python.debugger.containerview.NumericContainerRendererForm">
|
||||
<grid id="27dc6" binding="myMainPanel" layout-manager="GridLayoutManager" row-count="3" column-count="5" same-size-horizontally="false" same-size-vertically="false" hgap="-1" vgap="-1">
|
||||
<margin top="0" left="0" bottom="0" right="0"/>
|
||||
<constraints>
|
||||
<xy x="20" y="20" width="608" height="375"/>
|
||||
@@ -18,7 +18,7 @@
|
||||
</properties>
|
||||
<border type="none"/>
|
||||
<children>
|
||||
<component id="c7f7c" class="com.intellij.ui.table.JBTable" binding="myTable" custom-create="true">
|
||||
<component id="c7f7c" class="com.jetbrains.python.debugger.array.JBTableWithRowHeaders" binding="myTable" custom-create="true">
|
||||
<constraints/>
|
||||
<properties>
|
||||
<autoResizeMode value="0"/>
|
||||
@@ -0,0 +1,102 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.editor.ex.EditorEx;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.ui.EditorTextField;
|
||||
import com.intellij.ui.components.JBScrollPane;
|
||||
import com.intellij.ui.table.JBTable;
|
||||
import com.jetbrains.python.PythonFileType;
|
||||
import com.jetbrains.python.debugger.array.JBTableWithRowHeaders;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import javax.swing.table.JTableHeader;
|
||||
import java.awt.*;
|
||||
import java.awt.event.KeyListener;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 5/10/2016.
|
||||
*/
|
||||
public class NumericContainerRendererForm {
|
||||
protected final Project myProject;
|
||||
protected final KeyListener myReformatCallback;
|
||||
protected final KeyListener myResliceCallback;
|
||||
protected JBScrollPane myScrollPane;
|
||||
protected EditorTextField mySliceTextField;
|
||||
protected JBTableWithRowHeaders myTable;
|
||||
protected EditorTextField myFormatTextField;
|
||||
protected JCheckBox myColoredCheckbox;
|
||||
protected JPanel myFormatPanel;
|
||||
protected JPanel myMainPanel;
|
||||
protected JLabel myFormatLabel;
|
||||
|
||||
public NumericContainerRendererForm(
|
||||
@NotNull Project project, KeyListener resliceCallback, KeyListener reformatCallback) {
|
||||
myResliceCallback = resliceCallback;
|
||||
myProject = project;
|
||||
myReformatCallback = reformatCallback;
|
||||
}
|
||||
|
||||
public JBTable getTable() {
|
||||
return myTable;
|
||||
}
|
||||
|
||||
public JPanel getMainPanel() {
|
||||
return myMainPanel;
|
||||
}
|
||||
|
||||
public EditorTextField getSliceTextField() {
|
||||
return mySliceTextField;
|
||||
}
|
||||
|
||||
public JCheckBox getColoredCheckbox() {
|
||||
return myColoredCheckbox;
|
||||
}
|
||||
|
||||
public JBScrollPane getScrollPane() {
|
||||
return myScrollPane;
|
||||
}
|
||||
|
||||
public EditorTextField getFormatTextField() {
|
||||
return myFormatTextField;
|
||||
}
|
||||
|
||||
protected void createUIComponents() {
|
||||
mySliceTextField = new EditorTextField("", myProject, PythonFileType.INSTANCE) {
|
||||
@Override
|
||||
protected EditorEx createEditor() {
|
||||
EditorEx editor = super.createEditor();
|
||||
editor.getContentComponent().addKeyListener(myResliceCallback);
|
||||
return editor;
|
||||
}
|
||||
};
|
||||
|
||||
myFormatTextField = new EditorTextField("", myProject, PythonFileType.INSTANCE) {
|
||||
@Override
|
||||
protected EditorEx createEditor() {
|
||||
EditorEx editor = super.createEditor();
|
||||
editor.getContentComponent().addKeyListener(myReformatCallback);
|
||||
return editor;
|
||||
}
|
||||
};
|
||||
|
||||
myTable = new JBTableWithRowHeaders();
|
||||
myScrollPane = myTable.getScrollPane();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,283 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.diagnostic.Logger;
|
||||
import com.intellij.openapi.editor.RangeMarker;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.PyDebuggerException;
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import com.jetbrains.python.debugger.array.TableChunkDatasource;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman 5/10/2016.
|
||||
*/
|
||||
public abstract class NumericContainerViewTable implements TableChunkDatasource {
|
||||
protected final static int COLUMNS_IN_DEFAULT_VIEW = 1000;
|
||||
protected final static int ROWS_IN_DEFAULT_VIEW = 1000;
|
||||
protected static final Logger LOG = Logger.getInstance("#com.jetbrains.python.debugger.containerview.NumericContainerViewTable");
|
||||
protected final PyDebugValue myValue;
|
||||
protected final ViewNumericContainerDialog myDialog;
|
||||
protected final NumericContainerRendererForm myComponent;
|
||||
protected final JTable myTable;
|
||||
protected final Project myProject;
|
||||
protected String myDtypeKind;
|
||||
protected ColoredCellRenderer myTableCellRenderer;
|
||||
protected AsyncArrayTableModel myPagingModel;
|
||||
|
||||
public NumericContainerViewTable(
|
||||
@NotNull Project project, @NotNull ViewNumericContainerDialog dialog, @NotNull PyDebugValue value) {
|
||||
myProject = project;
|
||||
myDialog = dialog;
|
||||
myComponent = createForm(project, new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
doReslice();
|
||||
}
|
||||
}
|
||||
}, new KeyAdapter() {
|
||||
@Override
|
||||
public void keyPressed(KeyEvent e) {
|
||||
if (e.getKeyCode() == KeyEvent.VK_ENTER) {
|
||||
doApplyFormat();
|
||||
}
|
||||
}
|
||||
});
|
||||
myValue = value;
|
||||
myTable = myComponent.getTable();
|
||||
}
|
||||
|
||||
|
||||
private void initUi(@NotNull final ArrayChunk chunk, final boolean inPlace) {
|
||||
myPagingModel = createTableModel(Math.min(chunk.getRows(), ROWS_IN_DEFAULT_VIEW),
|
||||
Math.min(chunk.getColumns(), COLUMNS_IN_DEFAULT_VIEW));
|
||||
myPagingModel.addToCache(chunk);
|
||||
myDtypeKind = chunk.getType();
|
||||
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myTable.setModel(myPagingModel);
|
||||
myComponent.getSliceTextField().setText(chunk.getSlicePresentation());
|
||||
myComponent.getFormatTextField().setText(chunk.getFormat());
|
||||
myDialog.setTitle(getTitlePresentation(chunk.getSlicePresentation()));
|
||||
myTableCellRenderer = createCellRenderer(Double.MIN_VALUE, Double.MIN_VALUE, chunk);
|
||||
if (!isNumeric()) {
|
||||
disableColor();
|
||||
}
|
||||
else {
|
||||
myComponent.getColoredCheckbox().setEnabled(true);
|
||||
}
|
||||
|
||||
if (!inPlace) {
|
||||
myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0));
|
||||
}
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged();
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void disableColor() {
|
||||
myTableCellRenderer.setColored(false);
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myComponent.getColoredCheckbox().setSelected(false);
|
||||
myComponent.getColoredCheckbox().setEnabled(false);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected abstract AsyncArrayTableModel createTableModel(int rowCount, int columnCount);
|
||||
|
||||
protected abstract ColoredCellRenderer createCellRenderer(double minValue, double maxValue, ArrayChunk chunk);
|
||||
|
||||
private void initTableModel(final boolean inPlace) {
|
||||
myPagingModel = createTableModel(myPagingModel.getRowCount(), myPagingModel.getColumnCount());
|
||||
|
||||
UIUtil.invokeLaterIfNeeded(() -> {
|
||||
myTable.setModel(myPagingModel);
|
||||
if (!inPlace) {
|
||||
myComponent.getScrollPane().getViewport().setViewPosition(new Point(0, 0));
|
||||
}
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableDataChanged();
|
||||
((AsyncArrayTableModel)myTable.getModel()).fireTableCellUpdated(0, 0);
|
||||
if (myTable.getColumnCount() > 0) {
|
||||
myTable.setDefaultRenderer(myTable.getColumnClass(0), myTableCellRenderer);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@NotNull
|
||||
private static NumericContainerRendererForm createForm(@NotNull Project project, KeyListener resliceCallback, KeyAdapter formatCallback) {
|
||||
return new NumericContainerRendererForm(project, resliceCallback, formatCallback);
|
||||
}
|
||||
|
||||
protected abstract String getTitlePresentation(String slice);
|
||||
|
||||
|
||||
public final NumericContainerRendererForm getComponent() {
|
||||
return myComponent;
|
||||
}
|
||||
|
||||
private void initComponent() {
|
||||
myComponent.getColoredCheckbox().setEnabled(false);
|
||||
myComponent.getColoredCheckbox().addItemListener(e -> {
|
||||
if (e.getSource() == myComponent.getColoredCheckbox()) {
|
||||
|
||||
if (myTable.getRowCount() > 0 &&
|
||||
myTable.getColumnCount() > 0 &&
|
||||
myTable.getCellRenderer(0, 0) instanceof ColoredCellRenderer) {
|
||||
ColoredCellRenderer renderer = (ColoredCellRenderer)myTable.getCellRenderer(0, 0);
|
||||
if (myComponent.getColoredCheckbox().isSelected()) {
|
||||
renderer.setColored(true);
|
||||
}
|
||||
else {
|
||||
renderer.setColored(false);
|
||||
}
|
||||
}
|
||||
if (myTableCellRenderer != null) {
|
||||
myTableCellRenderer.setColored(myComponent.getColoredCheckbox().isSelected());
|
||||
}
|
||||
myComponent.getScrollPane().repaint();
|
||||
}
|
||||
});
|
||||
|
||||
//make value name read-only
|
||||
myComponent.getSliceTextField().addFocusListener(new FocusListener() {
|
||||
@Override
|
||||
public void focusGained(FocusEvent e) {
|
||||
myComponent.getSliceTextField().getDocument().createGuardedBlock(0, getNodeFullName().length());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void focusLost(FocusEvent e) {
|
||||
RangeMarker block = myComponent.getSliceTextField().getDocument().getRangeGuard(0, getNodeFullName().length());
|
||||
if (block != null) {
|
||||
myComponent.getSliceTextField().getDocument().removeGuardedBlock(block);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
public PyDebugValue getDebugValue() {
|
||||
return myValue;
|
||||
}
|
||||
|
||||
public void init() {
|
||||
init(getDebugValue().getEvaluationExpression(), false);
|
||||
}
|
||||
|
||||
public void init(final String slice, final boolean inPlace) {
|
||||
initComponent();
|
||||
myComponent.getSliceTextField().setText(slice);
|
||||
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
final PyDebugValue value = getDebugValue();
|
||||
PyDebugValue parent = value.getParent();
|
||||
final PyDebugValue slicedValue =
|
||||
new PyDebugValue(slice, value.getType(), null, value.getValue(), value.isContainer(), value.isReturnedVal(), value.isErrorOnEval(),
|
||||
parent, value.getFrameAccessor());
|
||||
|
||||
final String format = getFormat().isEmpty() ? "%" : getFormat();
|
||||
|
||||
try {
|
||||
initUi(value.getFrameAccessor()
|
||||
.getArrayItems(slicedValue, 0, 0, -1, -1, format), inPlace);
|
||||
}
|
||||
catch (PyDebuggerException e) {
|
||||
showError(e.getMessage());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
public String getSliceText() {
|
||||
return myComponent.getSliceTextField().getText();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ArrayChunk getChunk(int rowOffset, int colOffset, int rows, int cols) throws PyDebuggerException {
|
||||
final PyDebugValue slicedValue =
|
||||
new PyDebugValue(getSliceText(), myValue.getType(), myValue.getTypeQualifier(), myValue.getValue(), myValue.isContainer(),
|
||||
myValue.isErrorOnEval(), myValue.isReturnedVal(),
|
||||
myValue.getParent(), myValue.getFrameAccessor());
|
||||
|
||||
return myValue.getFrameAccessor().getArrayItems(slicedValue, rowOffset, colOffset, rows, cols, getFormat());
|
||||
}
|
||||
|
||||
public abstract boolean isNumeric();
|
||||
|
||||
|
||||
@Override
|
||||
public final String correctStringValue(@NotNull Object value) {
|
||||
if (value instanceof String) {
|
||||
String corrected = (String)value;
|
||||
if (isNumeric()) {
|
||||
if (corrected.startsWith("\'") || corrected.startsWith("\"")) {
|
||||
corrected = corrected.substring(1, corrected.length() - 1);
|
||||
}
|
||||
}
|
||||
return corrected;
|
||||
}
|
||||
else if (value instanceof Integer) {
|
||||
return Integer.toString((Integer)value);
|
||||
}
|
||||
return value.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void showError(String message) {
|
||||
myDialog.setError(message);
|
||||
}
|
||||
|
||||
protected final void doReslice() {
|
||||
clearErrorMessage();
|
||||
init(getSliceText(), false);
|
||||
}
|
||||
|
||||
private void clearErrorMessage() {
|
||||
showError(null);
|
||||
}
|
||||
|
||||
protected final void doApplyFormat() {
|
||||
reset();
|
||||
}
|
||||
|
||||
private void reset() {
|
||||
clearErrorMessage();
|
||||
initTableModel(true);
|
||||
}
|
||||
|
||||
public final String getNodeFullName() {
|
||||
return getDebugValue().getEvaluationExpression();
|
||||
}
|
||||
|
||||
public final String getFormat() {
|
||||
return myComponent.getFormatTextField().getText();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,87 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.util.Pair;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 5/10/2016.
|
||||
*/
|
||||
public class PyNumericViewUtil {
|
||||
private static final Pattern PY_COMPLEX_NUMBER = Pattern.compile("([+-]?[.\\d^j]*)([+-]?[e.\\d]*j)?");
|
||||
|
||||
/**
|
||||
* @return double presentation from [0:1] range
|
||||
*/
|
||||
public static double getRangedValue(String value, String type, double min, double max, String complexMax, String complexMin) {
|
||||
if ("iuf".contains(type)) {
|
||||
return (Double.parseDouble(value) - min) / (max - min);
|
||||
}
|
||||
else if ("b".equals(type)) {
|
||||
return value.equals("True") ? 1 : 0;
|
||||
}
|
||||
else if ("c".equals(type)) {
|
||||
return getComplexRangedValue(value, complexMax, complexMin);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
public static Color rangedValueToColor(double rangedValue)
|
||||
{
|
||||
//noinspection UseJBColor
|
||||
return new Color((int)Math.round(255 * rangedValue), 0, (int)Math.round(255 * (1 - rangedValue)), 130);
|
||||
}
|
||||
|
||||
/**
|
||||
* type complex128 in numpy is compared by next rule:
|
||||
* A + Bj > C +Dj if A > C or A == C and B > D
|
||||
*/
|
||||
private static double getComplexRangedValue(String value, String complexMax, String complexMin) {
|
||||
Pair<Double, Double> med = parsePyComplex(value);
|
||||
Pair<Double, Double> max = parsePyComplex(complexMax);
|
||||
Pair<Double, Double> min = parsePyComplex(complexMin);
|
||||
double range = (med.first - min.first) / (max.first - min.first);
|
||||
if (max.first.equals(min.first)) {
|
||||
range = (med.second - min.second) / (max.second - min.second);
|
||||
}
|
||||
return range;
|
||||
}
|
||||
|
||||
private static Pair<Double, Double> parsePyComplex(@NotNull String pyComplexValue) {
|
||||
if (pyComplexValue.startsWith("(") && pyComplexValue.endsWith(")")) {
|
||||
pyComplexValue = pyComplexValue.substring(1, pyComplexValue.length() - 1);
|
||||
}
|
||||
Matcher matcher = PY_COMPLEX_NUMBER.matcher(pyComplexValue);
|
||||
if (matcher.matches()) {
|
||||
String real = matcher.group(1);
|
||||
String imag = matcher.group(2);
|
||||
if (real.contains("j") && imag == null) {
|
||||
return new Pair<>(new Double(0.0), Double.parseDouble(real.substring(0, real.length() - 1)));
|
||||
}
|
||||
else {
|
||||
return new Pair<>(Double.parseDouble(real), Double.parseDouble(imag.substring(0, imag.length() - 1)));
|
||||
}
|
||||
}
|
||||
else {
|
||||
throw new IllegalArgumentException("Not a valid python complex value: " + pyComplexValue);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright 2000-2014 JetBrains s.r.o.
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@@ -13,33 +13,53 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.array;
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||
import com.intellij.openapi.actionSystem.DataContext;
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import com.intellij.xdebugger.impl.ui.tree.XDebuggerTree;
|
||||
import com.intellij.xdebugger.impl.ui.tree.actions.XDebuggerTreeActionBase;
|
||||
import com.intellij.xdebugger.impl.ui.tree.nodes.XValueNodeImpl;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.array.NumpyArrayTable;
|
||||
import com.jetbrains.python.debugger.dataframe.DataFrameTable;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.tree.TreePath;
|
||||
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
|
||||
public class PyViewArrayAction extends XDebuggerTreeActionBase {
|
||||
public class PyViewNumericContainerAction extends XDebuggerTreeActionBase {
|
||||
|
||||
@Override
|
||||
protected void perform(XValueNodeImpl node, @NotNull String nodeName, AnActionEvent e) {
|
||||
Project p = e.getProject();
|
||||
if (p != null && node != null && node.getValueContainer() instanceof PyDebugValue && node.isComputed()) {
|
||||
final ViewArrayDialog dialog = new ViewArrayDialog(p, (PyDebugValue)(node.getValueContainer()));
|
||||
PyDebugValue debugValue = (PyDebugValue)node.getValueContainer();
|
||||
String nodeType = debugValue.getType();
|
||||
final ViewNumericContainerDialog dialog;
|
||||
if ("ndarray".equals(nodeType)) {
|
||||
dialog = new ViewNumericContainerDialog(p, (dialogWrapper) -> {
|
||||
NumpyArrayTable arrayTable = new NumpyArrayTable(p, dialogWrapper, debugValue);
|
||||
arrayTable.init();
|
||||
return arrayTable.getComponent().getMainPanel();
|
||||
});
|
||||
}
|
||||
else if (("DataFrame".equals(nodeType))) {
|
||||
dialog = new ViewNumericContainerDialog(p, (dialogWrapper) -> {
|
||||
DataFrameTable dataFrameTable = new DataFrameTable(p, dialogWrapper, debugValue);
|
||||
dataFrameTable.init();
|
||||
return dataFrameTable.getComponent().getMainPanel();
|
||||
});
|
||||
}
|
||||
else {
|
||||
throw new IllegalStateException("Cannot render node type: " + nodeType);
|
||||
}
|
||||
|
||||
dialog.show();
|
||||
}
|
||||
}
|
||||
@@ -65,54 +85,21 @@ public class PyViewArrayAction extends XDebuggerTreeActionBase {
|
||||
|
||||
String nodeType = debugValue.getType();
|
||||
if ("ndarray".equals(nodeType)) {
|
||||
e.getPresentation().setText("View as Array");
|
||||
e.getPresentation().setVisible(true);
|
||||
return;
|
||||
}
|
||||
else if (("DataFrame".equals(nodeType))) {
|
||||
e.getPresentation().setText("View as DataFrame");
|
||||
e.getPresentation().setVisible(true);
|
||||
}
|
||||
else {
|
||||
e.getPresentation().setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
e.getPresentation().setVisible(false);
|
||||
}
|
||||
|
||||
protected static class ViewArrayDialog extends DialogWrapper {
|
||||
private Project myProject;
|
||||
private NumpyArrayTable myNumpyArrayTable;
|
||||
|
||||
private ViewArrayDialog(@NotNull Project project, PyDebugValue debugValue) {
|
||||
super(project, false);
|
||||
setModal(false);
|
||||
setCancelButtonText("Close");
|
||||
setCrossClosesWindow(true);
|
||||
|
||||
myProject = project;
|
||||
myNumpyArrayTable = new NumpyArrayTable(myProject, this, debugValue);
|
||||
|
||||
myNumpyArrayTable.init();
|
||||
init();
|
||||
}
|
||||
|
||||
public void setError(String text) {
|
||||
//todo: think about this usage
|
||||
setErrorText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getCancelAction()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.jetbrains.python.actions.view.array.PyViewArrayAction";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myNumpyArrayTable.getComponent().getMainPanel();
|
||||
}
|
||||
|
||||
public ArrayTableForm getComponent() {
|
||||
return myNumpyArrayTable.getComponent();
|
||||
else
|
||||
{
|
||||
e.getPresentation().setVisible(false);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.containerview;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.openapi.ui.DialogWrapper;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import javax.swing.*;
|
||||
import java.util.function.Function;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 5/10/2016.
|
||||
*/
|
||||
public class ViewNumericContainerDialog extends DialogWrapper {
|
||||
private final JComponent myMainPanel;
|
||||
|
||||
ViewNumericContainerDialog(@NotNull Project project, Function<ViewNumericContainerDialog, JComponent> tableSupplier) {
|
||||
super(project, false);
|
||||
setModal(false);
|
||||
setCancelButtonText("Close");
|
||||
setCrossClosesWindow(true);
|
||||
myMainPanel = tableSupplier.apply(this);
|
||||
init();
|
||||
}
|
||||
|
||||
public void setError(String text) {
|
||||
//todo: think about this usage
|
||||
setErrorText(text);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
protected Action[] createActions() {
|
||||
return new Action[]{getCancelAction()};
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getDimensionServiceKey() {
|
||||
return "#com.jetbrains.python.actions.view.array.PyViewNumericContainerAction";
|
||||
}
|
||||
|
||||
@Override
|
||||
protected JComponent createCenterPanel() {
|
||||
return myMainPanel;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.dataframe;
|
||||
|
||||
import com.intellij.openapi.project.Project;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.*;
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.NumericContainerViewTable;
|
||||
import com.jetbrains.python.debugger.array.TableChunkDatasource;
|
||||
import com.jetbrains.python.debugger.containerview.NumericContainerRendererForm;
|
||||
import com.jetbrains.python.debugger.containerview.ViewNumericContainerDialog;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.awt.*;
|
||||
import java.awt.event.*;
|
||||
|
||||
/**
|
||||
* @author amarch
|
||||
*/
|
||||
/* A bunch of this is copied from NumpyArrayTable*/
|
||||
|
||||
public class DataFrameTable extends NumericContainerViewTable implements TableChunkDatasource {
|
||||
|
||||
private Project myProject;
|
||||
|
||||
public DataFrameTable(@NotNull Project project,
|
||||
@NotNull ViewNumericContainerDialog dialog, @NotNull PyDebugValue value) {
|
||||
super(project, dialog, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected AsyncArrayTableModel createTableModel(int rowCount, int columnCount) {
|
||||
return new DataFrameTableModel(rowCount, columnCount, this);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ColoredCellRenderer createCellRenderer(double minValue, double maxValue, ArrayChunk chunk) {
|
||||
return new DataFrameTableCellRenderer();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isNumeric() {
|
||||
return true;
|
||||
}
|
||||
|
||||
protected final String getTitlePresentation(String slice) {
|
||||
return "DataFrame View: " + slice;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.dataframe;
|
||||
|
||||
import com.intellij.ui.JBColor;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.containerview.ColoredCellRenderer;
|
||||
import com.jetbrains.python.debugger.containerview.PyNumericViewUtil;
|
||||
|
||||
import javax.swing.*;
|
||||
import javax.swing.border.LineBorder;
|
||||
import javax.swing.table.DefaultTableCellRenderer;
|
||||
import java.awt.*;
|
||||
|
||||
|
||||
class DataFrameTableCellRenderer extends DefaultTableCellRenderer implements ColoredCellRenderer {
|
||||
|
||||
|
||||
private boolean myColored = true;
|
||||
|
||||
public DataFrameTableCellRenderer() {
|
||||
setHorizontalAlignment(CENTER);
|
||||
setHorizontalTextPosition(LEFT);
|
||||
setVerticalAlignment(BOTTOM);
|
||||
}
|
||||
|
||||
public void setColored(boolean colored) {
|
||||
myColored = colored;
|
||||
}
|
||||
|
||||
public Component getTableCellRendererComponent(JTable table, Object value,
|
||||
boolean isSelected, boolean hasFocus, int row, int col) {
|
||||
super.getTableCellRendererComponent(table, value, isSelected, hasFocus, row, col);
|
||||
if (value != null) {
|
||||
setText(value.toString());
|
||||
}
|
||||
|
||||
if (!(value instanceof TableValueDescriptor)) {
|
||||
return this;
|
||||
}
|
||||
|
||||
TableValueDescriptor descriptor = (TableValueDescriptor)value;
|
||||
|
||||
if (hasFocus) {
|
||||
this.setBorder(new LineBorder(JBColor.BLUE, 2));
|
||||
}
|
||||
|
||||
if (myColored) {
|
||||
try {
|
||||
double rangedValue = descriptor.getRangedValue();
|
||||
if (!Double.isNaN(rangedValue)) {
|
||||
this.setBackground(PyNumericViewUtil.rangedValueToColor(rangedValue));
|
||||
}
|
||||
}
|
||||
catch (NumberFormatException ignored) {
|
||||
|
||||
}
|
||||
}
|
||||
else {
|
||||
this.setBackground(new JBColor(UIUtil.getBgFillColor(table), UIUtil.getBgFillColor(table)));
|
||||
}
|
||||
|
||||
|
||||
return this;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,148 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.dataframe;
|
||||
|
||||
import com.google.common.collect.Maps;
|
||||
import com.intellij.util.ui.UIUtil;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import com.jetbrains.python.debugger.array.TableChunkDatasource;
|
||||
|
||||
import javax.swing.table.AbstractTableModel;
|
||||
import javax.swing.table.TableModel;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 4/26/2016.
|
||||
*/
|
||||
public class DataFrameTableModel extends AsyncArrayTableModel {
|
||||
private final Map<Integer, ArrayChunk.ColHeader> myColHeaders = Maps.newConcurrentMap();
|
||||
private final RowHeaderModel myRowHeaderModel;
|
||||
|
||||
public DataFrameTableModel(int rows, int columns, TableChunkDatasource provider) {
|
||||
|
||||
super(rows, columns, provider);
|
||||
myRowHeaderModel = new RowHeaderModel();
|
||||
}
|
||||
/* 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) {
|
||||
return value;
|
||||
}
|
||||
TableValueDescriptor descriptor = createValueWithDescriptor(col, value);
|
||||
return descriptor != null ? descriptor : AsyncArrayTableModel.EMPTY_CELL_VALUE;
|
||||
}
|
||||
|
||||
private TableValueDescriptor createValueWithDescriptor(int frameCol, Object value) {
|
||||
ArrayChunk.ColHeader header = myColHeaders.get(frameCol);
|
||||
if (header == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return new TableValueDescriptor(value.toString(), header);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public Class<?> getColumnClass(int columnIndex) {
|
||||
return TableValueDescriptor.class;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int col) {
|
||||
|
||||
|
||||
ArrayChunk.ColHeader header = myColHeaders.get(col);
|
||||
if (header != null && header.getLabel() != null) {
|
||||
return header.getLabel();
|
||||
}
|
||||
else {
|
||||
return super.getColumnName(col);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
protected void handleChunkAdded(Integer rowOffset, Integer colOffset, ArrayChunk chunk) {
|
||||
|
||||
myRowHeaderModel.handleChunkAdded(rowOffset, chunk);
|
||||
boolean hasNewCols = false;
|
||||
List<ArrayChunk.ColHeader> chunkColHeaders = chunk.getColHeaders();
|
||||
if (chunkColHeaders != null) {
|
||||
for (int i = 0; i < chunkColHeaders.size(); i++) {
|
||||
ArrayChunk.ColHeader header = chunkColHeaders.get(i);
|
||||
hasNewCols |= (myColHeaders.put(i + colOffset, header) == null);
|
||||
}
|
||||
}
|
||||
if (hasNewCols) {
|
||||
UIUtil.invokeLaterIfNeeded(super::fireTableStructureChanged);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public TableModel getRowHeaderModel() {
|
||||
return myRowHeaderModel;
|
||||
}
|
||||
|
||||
private class RowHeaderModel extends AbstractTableModel {
|
||||
|
||||
private final Map<Integer, String> myRowLabels = Maps.newConcurrentMap();
|
||||
|
||||
@Override
|
||||
public int getRowCount() {
|
||||
return DataFrameTableModel.this.getRowCount();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getColumnCount() {
|
||||
return 1;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getColumnName(int column) {
|
||||
if (column == 0) {
|
||||
return " ";
|
||||
}
|
||||
throw new IllegalArgumentException("Table only has one column");
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getValueAt(int rowIndex, int columnIndex) {
|
||||
String s = myRowLabels.get(rowIndex);
|
||||
return s == null ? String.valueOf(rowIndex) : s;
|
||||
}
|
||||
|
||||
public void handleChunkAdded(Integer rowOffset, ArrayChunk chunk) {
|
||||
List<String> chunkRowLabels = chunk.getRowLabels();
|
||||
if (chunkRowLabels != null) {
|
||||
for (int i = 0; i < chunkRowLabels.size(); i++) {
|
||||
String label = chunkRowLabels.get(i);
|
||||
String oldValue = myRowLabels.put(i + rowOffset, label);
|
||||
if (oldValue == null) {
|
||||
final int updatedRow = i + rowOffset;
|
||||
UIUtil.invokeLaterIfNeeded(() ->
|
||||
super.fireTableCellUpdated(updatedRow, 0));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,81 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.python.debugger.dataframe;
|
||||
|
||||
import com.google.common.base.Objects;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.containerview.PyNumericViewUtil;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 4/26/2016.
|
||||
*/
|
||||
public class TableValueDescriptor {
|
||||
private final String myValue;
|
||||
private final ArrayChunk.ColHeader myHeader;
|
||||
|
||||
public TableValueDescriptor(String value, ArrayChunk.ColHeader header) {
|
||||
myValue = value;
|
||||
myHeader = header;
|
||||
}
|
||||
|
||||
public String getValue() {
|
||||
return myValue;
|
||||
}
|
||||
|
||||
public double getRangedValue() {
|
||||
if (myValue == null || myHeader == null)
|
||||
{
|
||||
return Double.NaN;
|
||||
}
|
||||
String dataType = myHeader.getType();
|
||||
if ("o".equals(dataType))
|
||||
{
|
||||
return Double.NaN;
|
||||
}
|
||||
String minValue = Objects.firstNonNull(myHeader.getMin(), "0");
|
||||
String maxValue = Objects.firstNonNull(myHeader.getMax(), "0");
|
||||
|
||||
|
||||
double min;
|
||||
double max;
|
||||
if ("c".equals(dataType)) {
|
||||
min = 0;
|
||||
max = 1;
|
||||
}
|
||||
else if ("b".equals(dataType)) {
|
||||
min = minValue.equals("True") ? 1 : 0;
|
||||
max = maxValue.equals("True") ? 1 : 0;
|
||||
}
|
||||
else {
|
||||
min = Double.parseDouble(minValue);
|
||||
max = Double.parseDouble(maxValue);
|
||||
}
|
||||
|
||||
return (min == max)? 0 : PyNumericViewUtil.getRangedValue(myValue, dataType, min, max, minValue, maxValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
if (myValue == null || myHeader == null)
|
||||
{
|
||||
return "";
|
||||
}
|
||||
else
|
||||
{
|
||||
return myValue;
|
||||
}
|
||||
}
|
||||
}
|
||||
32
python/testData/debug/test_dataframe.py
Normal file
32
python/testData/debug/test_dataframe.py
Normal file
@@ -0,0 +1,32 @@
|
||||
import pandas as pd
|
||||
import numpy as np
|
||||
df1 = pd.DataFrame({'row': [0, 1, 2],
|
||||
'One_X': [1.1, 1.1, 1.1],
|
||||
'One_Y': [1.2, 1.2, 1.2],
|
||||
'Two_X': [1.11, 1.11, 1.11],
|
||||
'Two_Y': [1.22, 1.22, 1.22]})
|
||||
print(df1) ###line 8
|
||||
|
||||
df2 = pd.DataFrame({'row': [0, 1, 2],
|
||||
'One_X': [1.1, 1.1, 1.1],
|
||||
'One_Y': [1.2, 1.2, 1.2],
|
||||
'Two_X': [1.11, 1.11, 1.11],
|
||||
'Two_Y': [1.22, 1.22, 1.22],
|
||||
'LABELS': ['A', 'B', 'C']})
|
||||
print(df2) ##line 16
|
||||
|
||||
df3 = pd.DataFrame(data={'Province' : ['ON','QC','BC','AL','AL','MN','ON'],
|
||||
'City' : ['Toronto','Montreal','Vancouver','Calgary','Edmonton','Winnipeg','Windsor'],
|
||||
'Sales' : [13,6,16,8,4,3,1]})
|
||||
table = pd.pivot_table(df3,values=['Sales'],index=['Province'],columns=['City'],aggfunc=np.sum,margins=True)
|
||||
table.stack('City')
|
||||
print(df3)
|
||||
|
||||
df4 = pd.DataFrame({'row': np.random.random(10000),
|
||||
'One_X': np.random.random(10000),
|
||||
'One_Y': np.random.random(10000),
|
||||
'Two_X': np.random.random(10000),
|
||||
'Two_Y': np.random.random(10000),
|
||||
'LABELS': ['A'] * 10000})
|
||||
print(df4) ##line 31
|
||||
|
||||
83
python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java
vendored
Normal file
83
python/testSrc/com/jetbrains/env/python/PythonDataViewerTest.java
vendored
Normal file
@@ -0,0 +1,83 @@
|
||||
/*
|
||||
* Copyright 2000-2016 JetBrains s.r.o.
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.jetbrains.env.python;
|
||||
|
||||
import com.intellij.xdebugger.XDebuggerTestUtil;
|
||||
import com.jetbrains.env.PyEnvTestCase;
|
||||
import com.jetbrains.env.python.debug.PyDebuggerTask;
|
||||
import com.jetbrains.python.debugger.ArrayChunk;
|
||||
import com.jetbrains.python.debugger.PyDebugValue;
|
||||
import com.jetbrains.python.debugger.PyDebuggerException;
|
||||
import com.jetbrains.python.debugger.array.AsyncArrayTableModel;
|
||||
import org.junit.Test;
|
||||
|
||||
import static com.intellij.testFramework.UsefulTestCase.assertSameElements;
|
||||
import static org.junit.Assert.assertEquals;
|
||||
|
||||
/**
|
||||
* Created by Yuli Fiterman on 5/10/2016.
|
||||
*/
|
||||
public class PythonDataViewerTest extends PyEnvTestCase {
|
||||
|
||||
@Test
|
||||
public void testDataFrameChunkRetrieval() throws Exception {
|
||||
runPythonTest(new PyDebuggerTask("/debug", "test_dataframe.py") {
|
||||
@Override
|
||||
public void before() throws Exception {
|
||||
toggleBreakpoint(getScriptPath(), 7);
|
||||
toggleBreakpoint(getScriptPath(), 15);
|
||||
toggleBreakpoint(getScriptPath(), 22);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void testing() throws Exception {
|
||||
waitForPause();
|
||||
ArrayChunk df1 = getDefaultChunk("df1");
|
||||
assertEquals(5, df1.getColumns());
|
||||
assertEquals(3, df1.getRows());
|
||||
resume();
|
||||
|
||||
waitForPause();
|
||||
ArrayChunk df2 = getDefaultChunk("df2");
|
||||
assertEquals(6, df2.getColumns());
|
||||
assertEquals(3, df2.getRows());
|
||||
assertSameElements(df2.getColHeaders().stream().map((header-> header.getLabel())).toArray(), new String[]{"LABELS", "One_X", "One_Y", "Two_X", "Two_Y", "row"});
|
||||
resume();
|
||||
|
||||
waitForPause();
|
||||
ArrayChunk df3 = getDefaultChunk("df3");
|
||||
assertEquals(3, df3.getColumns());
|
||||
assertEquals(7, df3.getRows());
|
||||
ArrayChunk.ColHeader header = df3.getColHeaders().get(2);
|
||||
assertEquals("Sales", header.getLabel());
|
||||
assertEquals(16, (int)Integer.valueOf(header.getMax()));
|
||||
assertEquals(1, (int)Integer.valueOf(header.getMin()));
|
||||
resume();
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
private ArrayChunk getDefaultChunk(String varName) throws PyDebuggerException {
|
||||
PyDebugValue dbgVal = (PyDebugValue)XDebuggerTestUtil.evaluate(mySession, varName).first;
|
||||
return dbgVal.getFrameAccessor()
|
||||
.getArrayItems(dbgVal, 0, 0, -1, -1, ".%5f");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
Reference in New Issue
Block a user