Editor: field hints show the column's raw data type, not the encoding type

This commit is contained in:
2026-06-29 11:06:37 +03:00
parent 8aa05e503d
commit 01df58acf1
+14 -10
View File
@@ -10,8 +10,11 @@
* - **Hover** — a column's inferred type + cardinality/range (from the stored * - **Hover** — a column's inferred type + cardinality/range (from the stored
* profile); over a `calculate`/`filter`/`expr` string, a live validity check * profile); over a `calculate`/`filter`/`expr` string, a live validity check
* via core/expr-validate. Monaco merges these with the schema's own hovers. * via core/expr-validate. Monaco merges these with the schema's own hovers.
* - **Inlay hints** — a faint `: <type>` beside each `field`, annotation without * - **Inlay hints** — a faint `·<data-type>` beside each `field` (the column's
* touching the text. * raw type: number/string/date/boolean), annotation without touching the text.
* Deliberately the *data* type, not the encoding `type` — they share the line,
* so a `: quantitative` here would read as annotating the adjacent `"type"`,
* which the hint never describes.
* *
* All three read the active draft's bound dataset (services/active-dataset) and * All three read the active draft's bound dataset (services/active-dataset) and
* the cursor's JSON context (core/spec-cursor) at provide-time via `getState()` — * the cursor's JSON context (core/spec-cursor) at provide-time via `getState()` —
@@ -22,10 +25,8 @@
*/ */
import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main'; import * as monaco from 'monaco-editor/esm/vs/editor/edcore.main';
import { defaultFieldType } from '@core/chart-builder';
import { validateExpression } from '@core/expr-validate'; import { validateExpression } from '@core/expr-validate';
import { stringValueAtOffset, valueKeyAtOffset } from '@core/spec-cursor'; import { stringValueAtOffset, valueKeyAtOffset } from '@core/spec-cursor';
import type { ColumnType } from '@core/type-inference';
import { useSnippetStore } from '../stores/SnippetStore'; import { useSnippetStore } from '../stores/SnippetStore';
import { import {
availableFieldsAt, availableFieldsAt,
@@ -40,15 +41,14 @@ const FIELD_KEYS = new Set(['field', 'groupby']);
/** Property values that hold a Vega expression (where the validity check fires). */ /** Property values that hold a Vega expression (where the validity check fires). */
const EXPR_KEYS = new Set(['calculate', 'filter', 'expr']); const EXPR_KEYS = new Set(['calculate', 'filter', 'expr']);
/** A short type label for a source field; null type (derived) is shown elsewhere. */
const typeLabel = (type: ColumnType): string => defaultFieldType(type);
/** Markdown hover for a field hint: type + stats for source, a note for derived. */ /** Markdown hover for a field hint: type + stats for source, a note for derived. */
function fieldHoverContents(hint: FieldHint, info: DataInfo): { value: string }[] { function fieldHoverContents(hint: FieldHint, info: DataInfo): { value: string }[] {
if (hint.derived || hint.type === null) { if (hint.derived || hint.type === null) {
return [{ value: `**${hint.name}** · _derived by a transform_` }]; return [{ value: `**${hint.name}** · _derived by a transform_` }];
} }
const lines = [`**${hint.name}** · \`${typeLabel(hint.type)}\``]; // The column's raw data type (number/string/date/boolean) — same vocabulary as
// the inlay hint, not the Vega-Lite encoding `type` the user declares.
const lines = [`**${hint.name}** · \`${hint.type}\``];
const stat = info.columnStats.find((s) => s.name === hint.name); const stat = info.columnStats.find((s) => s.name === hint.name);
if (stat) { if (stat) {
if (stat.numericExtent) if (stat.numericExtent)
@@ -91,7 +91,7 @@ export function configureSpecDatasetHints(): void {
kind: f.derived kind: f.derived
? monaco.languages.CompletionItemKind.Variable ? monaco.languages.CompletionItemKind.Variable
: monaco.languages.CompletionItemKind.Field, : monaco.languages.CompletionItemKind.Field,
detail: f.derived || f.type === null ? 'derived field' : typeLabel(f.type), detail: f.derived || f.type === null ? 'derived field' : f.type,
insertText: f.name, insertText: f.name,
range, range,
})), })),
@@ -167,7 +167,11 @@ export function configureSpecDatasetHints(): void {
if (!type) continue; // unknown or derived (no type to annotate) if (!type) continue; // unknown or derived (no type to annotate)
hints.push({ hints.push({
position: { lineNumber: line, column: match.index + match[0].length + 1 }, position: { lineNumber: line, column: match.index + match[0].length + 1 },
label: `: ${typeLabel(type)}`, // The column's raw data type (number/string/date/boolean), not the
// Vega-Lite encoding `type`: a `: quantitative` here would read as an
// annotation of the adjacent `"type"`, which the hint never describes.
// The leading `·` marks it as a data fact about the field, not JSON syntax.
label: `·${type}`,
kind: monaco.languages.InlayHintKind.Type, kind: monaco.languages.InlayHintKind.Type,
paddingLeft: true, paddingLeft: true,
}); });