2.4.0
This commit is contained in:
@@ -1,96 +1,95 @@
|
||||
import {
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
*/
|
||||
registerExtractor(extractor: DataExtractor): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
*/
|
||||
registerExtractors(extractors: DataExtractor[]): void;
|
||||
|
||||
/**
|
||||
* Extracts data from the result of `valueFn`.
|
||||
* @valueFn a function returning the value to extract the data from.
|
||||
* Is a function so that it's evaluation can depend on `evalFn`.
|
||||
*/
|
||||
getData(
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, unknown>
|
||||
): JSONString<DataResult>;
|
||||
|
||||
/**
|
||||
* Registers all default (built-in) extractors.
|
||||
* @preferExisting if `true`, existing extractors with the same id are not overwritten.
|
||||
*/
|
||||
registerDefaultExtractors(preferExisting?: boolean): void;
|
||||
|
||||
registerDataExtractorsSource(id: string, fn: LoadDataExtractorsFn): void;
|
||||
unregisterDataExtractorsSource(id: string): void;
|
||||
}
|
||||
|
||||
export type DataResult =
|
||||
| {
|
||||
kind: "Data";
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" }
|
||||
| { kind: "Error"; message: string };
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractor {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
/**
|
||||
* Evaluates an expression in the context of the active stack frame.
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
|
||||
variablesInScope: Record<string, () => unknown>;
|
||||
}
|
||||
|
||||
export interface DataExtraction {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* A unique id identifying this extraction among all extractions.
|
||||
* Required to express extraction preferences.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): VisualizationData;
|
||||
}
|
||||
import {
|
||||
VisualizationData,
|
||||
DataExtractionResult,
|
||||
} from "../../DataExtractionResult";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
|
||||
export interface DataExtractorApi {
|
||||
/**
|
||||
* Registers a single extractor.
|
||||
*/
|
||||
registerExtractor(extractor: DataExtractor): void;
|
||||
|
||||
/**
|
||||
* Registers multiple extractors.
|
||||
*/
|
||||
registerExtractors(extractors: DataExtractor[]): void;
|
||||
|
||||
/**
|
||||
* Extracts data from the result of `valueFn`.
|
||||
* @valueFn a function returning the value to extract the data from.
|
||||
* Is a function so that it's evaluation can depend on `evalFn`.
|
||||
*/
|
||||
getData(
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, unknown>
|
||||
): JSONString<DataResult>;
|
||||
|
||||
/**
|
||||
* Registers all default (built-in) extractors.
|
||||
* @preferExisting if `true`, existing extractors with the same id are not overwritten.
|
||||
*/
|
||||
registerDefaultExtractors(preferExisting?: boolean): void;
|
||||
|
||||
setDataExtractorFn(id: string, fn: LoadDataExtractorsFn | undefined): void;
|
||||
}
|
||||
|
||||
export type DataResult =
|
||||
| {
|
||||
kind: "Data";
|
||||
extractionResult: DataExtractionResult;
|
||||
}
|
||||
| { kind: "NoExtractors" }
|
||||
| { kind: "Error"; message: string };
|
||||
|
||||
export interface JSONString<T> extends String {
|
||||
__brand: { json: T };
|
||||
}
|
||||
|
||||
export interface DataExtractor {
|
||||
/**
|
||||
* Must be unique among all data extractors.
|
||||
*/
|
||||
id: string;
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
extractionCollector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void;
|
||||
}
|
||||
|
||||
export interface ExtractionCollector {
|
||||
/**
|
||||
* Suggests a possible extraction.
|
||||
*/
|
||||
addExtraction(extraction: DataExtraction): void;
|
||||
}
|
||||
|
||||
export interface DataExtractorContext {
|
||||
/**
|
||||
* Evaluates an expression in the context of the active stack frame.
|
||||
*/
|
||||
evalFn: <TEval>(expression: string) => TEval;
|
||||
|
||||
variablesInScope: Record<string, () => unknown>;
|
||||
}
|
||||
|
||||
export interface DataExtraction {
|
||||
/**
|
||||
* Higher priorities are preferred.
|
||||
*/
|
||||
priority: number;
|
||||
|
||||
/**
|
||||
* A unique id identifying this extraction among all extractions.
|
||||
* Required to express extraction preferences.
|
||||
*/
|
||||
id: string;
|
||||
/**
|
||||
* A user friendly name of this extraction.
|
||||
*/
|
||||
name: string;
|
||||
extractData(): VisualizationData;
|
||||
}
|
||||
|
||||
@@ -1,122 +1,122 @@
|
||||
import {
|
||||
DataExtractorApi,
|
||||
DataResult,
|
||||
JSONString,
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "./DataExtractorApi";
|
||||
import { DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
import * as helpers from "../helpers";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
public static lastContext: DataExtractorContext | undefined = undefined;
|
||||
|
||||
private readonly extractors = new Map<string, DataExtractor>();
|
||||
private readonly extractorSources = new Map<string, LoadDataExtractorsFn>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
|
||||
public registerExtractor(extractor: DataExtractor): void {
|
||||
this.extractors.set(extractor.id, extractor);
|
||||
}
|
||||
|
||||
public registerExtractors(extractors: DataExtractor[]): void {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
}
|
||||
}
|
||||
|
||||
public getData(
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, () => unknown>
|
||||
): JSONString<DataResult> {
|
||||
const extractions = new Array<DataExtraction>();
|
||||
const extractionCollector: ExtractionCollector = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
const context: DataExtractorContext = {
|
||||
evalFn,
|
||||
variablesInScope,
|
||||
};
|
||||
|
||||
DataExtractorApiImpl.lastContext = context;
|
||||
const value = valueFn();
|
||||
|
||||
const extractors = new Array<DataExtractor>();
|
||||
|
||||
for (const fn of this.extractorSources.values()) {
|
||||
fn((extractor) => {
|
||||
extractors.push(extractor);
|
||||
}, helpers);
|
||||
}
|
||||
|
||||
for (const e of [...this.extractors.values(), ...extractors]) {
|
||||
e.getExtractions(value, extractionCollector, context);
|
||||
}
|
||||
|
||||
DataExtractorApiImpl.lastContext = undefined;
|
||||
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return this.toJson({ kind: "NoExtractors" } as DataResult);
|
||||
}
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
(e) => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return this.toJson({
|
||||
kind: "Data",
|
||||
extractionResult: {
|
||||
data,
|
||||
usedExtractor: mapExtractor(usedExtraction),
|
||||
availableExtractors: extractions.map(mapExtractor),
|
||||
},
|
||||
} as DataResult);
|
||||
}
|
||||
|
||||
public registerDefaultExtractors(preferExisting: boolean = false): void {
|
||||
// TODO consider preferExisting
|
||||
registerDefaultExtractors(this);
|
||||
}
|
||||
|
||||
public registerDataExtractorsSource(
|
||||
id: string,
|
||||
fn: LoadDataExtractorsFn
|
||||
): void {
|
||||
this.extractorSources.set(id, fn);
|
||||
}
|
||||
|
||||
public unregisterDataExtractorsSource(id: string): void {
|
||||
this.extractorSources.delete(id);
|
||||
}
|
||||
}
|
||||
import {
|
||||
DataExtractorApi,
|
||||
DataResult,
|
||||
JSONString,
|
||||
DataExtractor,
|
||||
DataExtraction,
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "./DataExtractorApi";
|
||||
import { DataExtractorInfo } from "../../DataExtractionResult";
|
||||
import { registerDefaultExtractors } from "./default-extractors";
|
||||
import { LoadDataExtractorsFn } from "./LoadDataExtractorsFn";
|
||||
import * as helpers from "../helpers";
|
||||
|
||||
/**
|
||||
* @internal
|
||||
*/
|
||||
export class DataExtractorApiImpl implements DataExtractorApi {
|
||||
public static lastContext: DataExtractorContext | undefined = undefined;
|
||||
|
||||
private readonly extractors = new Map<string, DataExtractor>();
|
||||
private readonly extractorSources = new Map<string, LoadDataExtractorsFn>();
|
||||
|
||||
private toJson<TData>(data: TData): JSONString<TData> {
|
||||
return JSON.stringify(data) as any;
|
||||
}
|
||||
|
||||
public registerExtractor(extractor: DataExtractor): void {
|
||||
this.extractors.set(extractor.id, extractor);
|
||||
}
|
||||
|
||||
public registerExtractors(extractors: DataExtractor[]): void {
|
||||
for (const e of extractors) {
|
||||
this.registerExtractor(e);
|
||||
}
|
||||
}
|
||||
|
||||
public getData(
|
||||
valueFn: () => unknown,
|
||||
evalFn: <T>(expression: string) => T,
|
||||
preferredDataExtractorId: string | undefined,
|
||||
variablesInScope: Record<string, () => unknown>
|
||||
): JSONString<DataResult> {
|
||||
const extractions = new Array<DataExtraction>();
|
||||
const extractionCollector: ExtractionCollector = {
|
||||
addExtraction(extraction) {
|
||||
extractions.push(extraction);
|
||||
},
|
||||
};
|
||||
|
||||
const context: DataExtractorContext = {
|
||||
evalFn,
|
||||
variablesInScope,
|
||||
};
|
||||
|
||||
DataExtractorApiImpl.lastContext = context;
|
||||
const value = valueFn();
|
||||
|
||||
const extractors = new Array<DataExtractor>();
|
||||
|
||||
for (const fn of this.extractorSources.values()) {
|
||||
fn((extractor) => {
|
||||
extractors.push(extractor);
|
||||
}, helpers);
|
||||
}
|
||||
|
||||
for (const e of [...this.extractors.values(), ...extractors]) {
|
||||
e.getExtractions(value, extractionCollector, context);
|
||||
}
|
||||
|
||||
DataExtractorApiImpl.lastContext = undefined;
|
||||
|
||||
extractions.sort((a, b) => b.priority - a.priority);
|
||||
let usedExtraction = extractions[0];
|
||||
if (!usedExtraction) {
|
||||
return this.toJson({ kind: "NoExtractors" } as DataResult);
|
||||
}
|
||||
|
||||
if (preferredDataExtractorId) {
|
||||
const preferred = extractions.find(
|
||||
(e) => e.id === preferredDataExtractorId
|
||||
);
|
||||
if (preferred) {
|
||||
usedExtraction = preferred;
|
||||
}
|
||||
}
|
||||
|
||||
function mapExtractor(e: DataExtraction): DataExtractorInfo {
|
||||
return {
|
||||
id: e.id as any,
|
||||
name: e.name,
|
||||
priority: e.priority,
|
||||
};
|
||||
}
|
||||
|
||||
const data = usedExtraction.extractData();
|
||||
return this.toJson({
|
||||
kind: "Data",
|
||||
extractionResult: {
|
||||
data,
|
||||
usedExtractor: mapExtractor(usedExtraction),
|
||||
availableExtractors: extractions.map(mapExtractor),
|
||||
},
|
||||
} as DataResult);
|
||||
}
|
||||
|
||||
public registerDefaultExtractors(preferExisting: boolean = false): void {
|
||||
// TODO consider preferExisting
|
||||
registerDefaultExtractors(this);
|
||||
}
|
||||
|
||||
public setDataExtractorFn(
|
||||
id: string,
|
||||
fn: LoadDataExtractorsFn | undefined
|
||||
): void {
|
||||
if (fn) {
|
||||
this.extractorSources.set(id, fn);
|
||||
} else {
|
||||
this.extractorSources.delete(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
import { DataExtractor, ExtractionCollector, DataExtractorContext } from "..";
|
||||
import { LineColumnRange } from "../../../CommonDataTypes";
|
||||
import { asData, markedGrid, tryEval } from "../../helpers";
|
||||
|
||||
export class MarkedGridFromArrayExtractor implements DataExtractor {
|
||||
readonly id = "markedGridFromArray";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
return;
|
||||
}
|
||||
if (!Array.isArray(data[0])) {
|
||||
return;
|
||||
}
|
||||
|
||||
let markers: any;
|
||||
if (data.length === 2 && typeof data[1] === "object") {
|
||||
markers = data[1];
|
||||
} else {
|
||||
for (let i = 1; i < data.length; i++) {
|
||||
if (typeof data[i] !== "string") {
|
||||
return;
|
||||
}
|
||||
}
|
||||
markers = tryEval(data.slice(1));
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
id: "markedGridFromArray",
|
||||
name: "Marked Grid from Array",
|
||||
priority: 1000,
|
||||
extractData() {
|
||||
return markedGrid(data[0], markers as any);
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -4,7 +4,12 @@ import {
|
||||
ExtractionCollector,
|
||||
DataExtractorContext,
|
||||
} from "../DataExtractorApi";
|
||||
import { createGraph, CreateGraphEdge } from "../../helpers";
|
||||
import {
|
||||
createGraph,
|
||||
CreateGraphEdge,
|
||||
createGraphFromPointers,
|
||||
} from "../../helpers";
|
||||
import { GraphNode } from "../../..";
|
||||
|
||||
export class ObjectGraphExtractor implements DataExtractor {
|
||||
readonly id = "object-graph";
|
||||
@@ -27,53 +32,83 @@ export class ObjectGraphExtractor implements DataExtractor {
|
||||
return;
|
||||
}
|
||||
|
||||
const infoSelector: (item: object) => {
|
||||
id?: string | number;
|
||||
edges: CreateGraphEdge<object>[];
|
||||
} & Omit<GraphNode, "id"> = (item) => {
|
||||
let label = "";
|
||||
const edges = new Array<CreateGraphEdge<any>>();
|
||||
if (item instanceof Set) {
|
||||
label = "Set";
|
||||
for (const value of item.values()) {
|
||||
if (isObject(value)) {
|
||||
edges.push({ label: "item", to: value });
|
||||
}
|
||||
}
|
||||
} else if (item instanceof Map) {
|
||||
label = "Map";
|
||||
for (const [key, value] of item.entries()) {
|
||||
if (isObject(value)) {
|
||||
edges.push({ label: key, to: value });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [key, val] of Object.entries(item)) {
|
||||
if (isObject(val)) {
|
||||
edges.push({ label: key, to: val });
|
||||
}
|
||||
}
|
||||
|
||||
const className = item.constructor
|
||||
? item.constructor.name
|
||||
: "?";
|
||||
label = className;
|
||||
if (!Array.isArray(item)) {
|
||||
try {
|
||||
let toStrVal = item.toString();
|
||||
if (toStrVal !== "[object Object]") {
|
||||
if (toStrVal.length > 15) {
|
||||
toStrVal += ` "${toStrVal.substr(0, 15)}..."`;
|
||||
}
|
||||
label += `${className}: ${toStrVal}`;
|
||||
}
|
||||
} catch (e) {}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
shape: "box",
|
||||
edges,
|
||||
color: item === data ? "lightblue" : undefined,
|
||||
label,
|
||||
};
|
||||
};
|
||||
|
||||
collector.addExtraction({
|
||||
id: "object-graph",
|
||||
name: "Object Graph",
|
||||
priority: 99,
|
||||
priority: 98,
|
||||
extractData() {
|
||||
return createGraph(
|
||||
[data],
|
||||
item => {
|
||||
let label = "";
|
||||
const edges = new Array<CreateGraphEdge<any>>();
|
||||
if (item instanceof Set) {
|
||||
label = "Set";
|
||||
for (const value of item.values()) {
|
||||
if (isObject(value)) {
|
||||
edges.push({ label: "item", to: value });
|
||||
}
|
||||
}
|
||||
} else if (item instanceof Map) {
|
||||
label = "Map";
|
||||
for (const [key, value] of item.entries()) {
|
||||
if (isObject(value)) {
|
||||
edges.push({ label: key, to: value });
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (const [key, val] of Object.entries(item)) {
|
||||
if (isObject(val)) {
|
||||
edges.push({ label: key, to: val });
|
||||
}
|
||||
}
|
||||
|
||||
const className = item.constructor
|
||||
? item.constructor.name
|
||||
: "?";
|
||||
label = className;
|
||||
}
|
||||
|
||||
return {
|
||||
shape: "box",
|
||||
edges,
|
||||
color: item === data ? "lightblue" : undefined,
|
||||
label,
|
||||
};
|
||||
},
|
||||
{ maxSize: 50 }
|
||||
);
|
||||
return createGraph([data], infoSelector, { maxSize: 50 });
|
||||
},
|
||||
});
|
||||
|
||||
if (
|
||||
data.constructor === Object &&
|
||||
Object.values(data).every(
|
||||
(v) => v === undefined || v === null || typeof v === "object"
|
||||
)
|
||||
) {
|
||||
collector.addExtraction({
|
||||
id: "object-graph-pointers",
|
||||
name: "Object Graph With Pointers",
|
||||
priority: 99,
|
||||
extractData() {
|
||||
return createGraphFromPointers(data as any, infoSelector, {
|
||||
maxSize: 50,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,22 +1,33 @@
|
||||
import { DataExtractor, DataExtractorContext, ExtractionCollector } from "../..";
|
||||
|
||||
export class StringDiffExtractor implements DataExtractor {
|
||||
public readonly id = "string-diff";
|
||||
|
||||
public getExtractions(data: unknown,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext) {
|
||||
if (Array.isArray(data) && data.length === 2 && typeof data[0] === "string" && typeof data[1] === "string") {
|
||||
collector.addExtraction({
|
||||
id: "string-diff",
|
||||
name: "String Diff",
|
||||
priority: 1000,
|
||||
extractData: () => ({
|
||||
kind: { text: true },
|
||||
text: data[0],
|
||||
otherText: data[1]
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
import {
|
||||
DataExtractor,
|
||||
DataExtractorContext,
|
||||
ExtractionCollector,
|
||||
} from "../..";
|
||||
|
||||
export class StringDiffExtractor implements DataExtractor {
|
||||
public readonly id = "string-diff";
|
||||
|
||||
public getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
) {
|
||||
if (
|
||||
Array.isArray(data) &&
|
||||
data.length === 2 &&
|
||||
typeof data[0] === "string" &&
|
||||
typeof data[1] === "string"
|
||||
) {
|
||||
collector.addExtraction({
|
||||
id: "string-diff",
|
||||
name: "String Diff",
|
||||
priority: 1300,
|
||||
extractData: () => ({
|
||||
kind: { text: true },
|
||||
text: data[0],
|
||||
otherText: data[1],
|
||||
}),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,112 @@
|
||||
import { DataExtractor, ExtractionCollector, DataExtractorContext } from "..";
|
||||
import { LineColumnRange } from "../../../CommonDataTypes";
|
||||
import { asData } from "../../helpers";
|
||||
|
||||
export class StringRangeExtractor implements DataExtractor {
|
||||
readonly id = "stringRange";
|
||||
getExtractions(
|
||||
data: unknown,
|
||||
collector: ExtractionCollector,
|
||||
context: DataExtractorContext
|
||||
): void {
|
||||
if (!Array.isArray(data)) {
|
||||
return;
|
||||
}
|
||||
if (typeof data[0] !== "string") {
|
||||
return;
|
||||
}
|
||||
|
||||
const text = data[0];
|
||||
|
||||
const decorations1: { range: LineColumnRange; label?: string }[] = [];
|
||||
const decorations2: { range: LineColumnRange; label?: string }[] = [];
|
||||
|
||||
function offsetToLineColumn(offset: number) {
|
||||
let line = 0;
|
||||
let column = 0;
|
||||
for (let idx = 0; idx < text.length; idx++) {
|
||||
if (idx === offset) {
|
||||
return { line, column };
|
||||
}
|
||||
if (text[idx] === "\n") {
|
||||
line++;
|
||||
column = 0;
|
||||
} else {
|
||||
column++;
|
||||
}
|
||||
}
|
||||
return { line, column }; // TODO
|
||||
}
|
||||
|
||||
if (
|
||||
data.length === 2 &&
|
||||
typeof data[1] === "object" &&
|
||||
!Array.isArray(data[1])
|
||||
) {
|
||||
data = [data[0], ...Object.values(data[1])] as [
|
||||
number | [number, number]
|
||||
][];
|
||||
}
|
||||
|
||||
for (let item of (data as (number | [number, number])[]).slice(1)) {
|
||||
if (typeof item === "string") {
|
||||
item = context.evalFn(item);
|
||||
if (item === undefined) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if (typeof item === "number") {
|
||||
const pos = offsetToLineColumn(item);
|
||||
decorations1.push({ range: { start: pos, end: pos } });
|
||||
decorations2.push({
|
||||
range: {
|
||||
start: pos,
|
||||
end: { line: pos.line, column: pos.column + 1 },
|
||||
},
|
||||
});
|
||||
} else if (
|
||||
Array.isArray(item) &&
|
||||
item.length === 2 &&
|
||||
typeof item[0] === "number" &&
|
||||
typeof item[1] === "number"
|
||||
) {
|
||||
decorations1.push({
|
||||
range: {
|
||||
start: offsetToLineColumn(item[0]),
|
||||
end: offsetToLineColumn(item[1]),
|
||||
},
|
||||
});
|
||||
decorations2.push(decorations1[decorations1.length - 1]);
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
collector.addExtraction({
|
||||
priority: 1200,
|
||||
id: "stringRange",
|
||||
name: "String Range",
|
||||
extractData() {
|
||||
return asData({
|
||||
kind: { text: true },
|
||||
text,
|
||||
decorations: decorations1,
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
collector.addExtraction({
|
||||
priority: 1000,
|
||||
id: "stringRangeFullCharacters",
|
||||
name: "String Range (Full Character)",
|
||||
extractData() {
|
||||
return asData({
|
||||
kind: { text: true },
|
||||
text,
|
||||
decorations: decorations2,
|
||||
});
|
||||
},
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -16,7 +16,9 @@ export class TableDataExtractor implements DataExtractor {
|
||||
if (!Array.isArray(data)) {
|
||||
return;
|
||||
}
|
||||
if (!data.every(d => typeof d === "object" && d)) {
|
||||
if (
|
||||
!data.every((d) => typeof d === "object" && d && !Array.isArray(d))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
assert<object[]>(data);
|
||||
@@ -44,7 +46,7 @@ export class TableDataExtractor implements DataExtractor {
|
||||
kind: {
|
||||
table: true,
|
||||
},
|
||||
rows: data.map(d => ({ type: d.constructor.name, ...d })),
|
||||
rows: data.map((d) => ({ type: d.constructor.name, ...d })),
|
||||
});
|
||||
},
|
||||
});
|
||||
|
||||
@@ -9,6 +9,8 @@ import { getDataExtractorApi } from "../injection";
|
||||
import { GridExtractor } from "./GridExtractor";
|
||||
import { TableDataExtractor } from "./TableExtractor";
|
||||
import { StringDiffExtractor } from "./StringDiffExtractor";
|
||||
import { StringRangeExtractor } from "./StringRangeExtractor";
|
||||
import { MarkedGridFromArrayExtractor } from "./MarkedGridExtractor";
|
||||
|
||||
/**
|
||||
* The default data extractors should be registered by VS Code automatically.
|
||||
@@ -27,6 +29,8 @@ export function registerDefaultExtractors(
|
||||
new GridExtractor(),
|
||||
new TableDataExtractor(),
|
||||
new StringDiffExtractor(),
|
||||
new StringRangeExtractor(),
|
||||
new MarkedGridFromArrayExtractor(),
|
||||
]) {
|
||||
api.registerExtractor(item);
|
||||
}
|
||||
|
||||
@@ -7,12 +7,11 @@ import { CreateGraphEdge, createGraph } from "./createGraph";
|
||||
*/
|
||||
export function createGraphFromPointers<T>(
|
||||
roots: Record<string, T | undefined | null>,
|
||||
infoSelector: (
|
||||
item: T
|
||||
) => {
|
||||
infoSelector: (item: T) => {
|
||||
id?: string | number;
|
||||
edges: CreateGraphEdge<T>[];
|
||||
} & Omit<GraphNode, "id">
|
||||
} & Omit<GraphNode, "id">,
|
||||
options: { maxSize?: number } = {}
|
||||
): GraphVisualizationData {
|
||||
const marker = {};
|
||||
|
||||
@@ -28,18 +27,22 @@ export function createGraphFromPointers<T>(
|
||||
value,
|
||||
}));
|
||||
|
||||
return createGraph<T | Pointer>(items, item => {
|
||||
if ("marker" in item && item["marker"] === marker) {
|
||||
return {
|
||||
id: "label____" + item.name,
|
||||
color: "orange",
|
||||
label: item.name,
|
||||
edges: [{ to: item.value!, color: "orange", label: "" }].filter(
|
||||
t => !!t.to
|
||||
),
|
||||
};
|
||||
} else {
|
||||
return infoSelector(item as T);
|
||||
}
|
||||
});
|
||||
return createGraph<T | Pointer>(
|
||||
items,
|
||||
(item) => {
|
||||
if ("marker" in item && item["marker"] === marker) {
|
||||
return {
|
||||
id: "label____" + item.name,
|
||||
color: "orange",
|
||||
label: item.name,
|
||||
edges: [
|
||||
{ to: item.value!, color: "orange", label: "" },
|
||||
].filter((t) => !!t.to),
|
||||
};
|
||||
} else {
|
||||
return infoSelector(item as T);
|
||||
}
|
||||
},
|
||||
options
|
||||
);
|
||||
}
|
||||
|
||||
@@ -4,15 +4,28 @@ export function markedGrid(
|
||||
arr: unknown[],
|
||||
marked: Record<string, number | undefined>
|
||||
): GridVisualizationData {
|
||||
if (!Array.isArray(arr)) {
|
||||
arr = [...(arr as any)];
|
||||
}
|
||||
|
||||
return {
|
||||
kind: { grid: true },
|
||||
rows: [{ columns: arr.map(d => ({ content: "" + d, tag: "" + d })) }],
|
||||
markers: Object.entries(marked)
|
||||
.map(([key, val]) => ({
|
||||
id: "" + key,
|
||||
row: 0,
|
||||
column: val!,
|
||||
}))
|
||||
.filter(c => c.column !== undefined),
|
||||
rows: [
|
||||
{
|
||||
columns: arr.map((d) => ({
|
||||
content: "" + d,
|
||||
tag: "" + d,
|
||||
})),
|
||||
},
|
||||
],
|
||||
markers: marked
|
||||
? Object.entries(marked)
|
||||
.map(([key, val]) => ({
|
||||
id: "" + key,
|
||||
row: 0,
|
||||
column: val!,
|
||||
}))
|
||||
.filter((c) => c.column !== undefined)
|
||||
: undefined,
|
||||
};
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user