mirror of
https://github.com/l0ng-ai/openidea.git
synced 2026-11-14 16:07:52 +00:00
Refs #2333 Editing json / jsonb / MySQL JSON cells showed one long plain text line, and invalid JSON only failed on Submit. The Value panel and the grid's ⇧Enter window now open the IDE's code editor for JSON cells, pretty-printed (big integers and duplicate keys preserved, large values formatted off the main thread) with highlighting, folding and find; validation as you type blocks Apply only for JSON columns; Reformat; Apply of an unchanged value creates no pending edit, and drafts survive opening the window. XML and bytea modes from #2333 remain. Co-authored-by: Inf1mum <8135225+Inf1mum@users.noreply.github.com>
77 lines
2.6 KiB
TypeScript
77 lines
2.6 KiB
TypeScript
import type { Cell } from "./model";
|
|
|
|
/** Automatic formatting and content detection are bounded on the UI thread. */
|
|
export const JSON_INLINE_LIMIT = 256_000;
|
|
|
|
export const isJsonColumn = (type: string) => /^(json|jsonb|MYSQL_TYPE_JSON)$/i.test(type.trim());
|
|
|
|
export interface JsonError {
|
|
message: string;
|
|
pos: number;
|
|
}
|
|
|
|
export function jsonErrorOf(text: string): JsonError | null {
|
|
try {
|
|
JSON.parse(text);
|
|
return null;
|
|
} catch (e) {
|
|
const message = e instanceof Error ? e.message : String(e);
|
|
const at = /position (\d+)/i.exec(message);
|
|
return { message, pos: at ? Math.min(Number(at[1]), text.length) : 0 };
|
|
}
|
|
}
|
|
|
|
/** Content detection chooses highlighting, never the column's validation rules. */
|
|
export function isJsonValue(columnType: string, cell: Cell): boolean {
|
|
if (isJsonColumn(columnType)) return true;
|
|
if (cell === null || cell.length > JSON_INLINE_LIMIT) return false;
|
|
const text = cell.trim();
|
|
return (text.startsWith("{") || text.startsWith("[")) && jsonErrorOf(text) === null;
|
|
}
|
|
|
|
/** Change whitespace only: parsing/stringifying would round integers and discard duplicate keys. */
|
|
export function formatJson(text: string): string {
|
|
if (jsonErrorOf(text)) return text;
|
|
const tokens: string[] = [];
|
|
// Scan strings directly: WebKit can stop a repeated regular expression on a large literal.
|
|
for (let i = 0; i < text.length;) {
|
|
const start = i;
|
|
const char = text[i++]!;
|
|
if (" \t\r\n".includes(char)) continue;
|
|
if (char === '"') {
|
|
while (i < text.length) {
|
|
const next = text[i++]!;
|
|
if (next === "\\") i++;
|
|
else if (next === '"') break;
|
|
}
|
|
} else if (!"{}[],:".includes(char)) {
|
|
while (i < text.length && !" \t\r\n{}[],:".includes(text[i]!)) i++;
|
|
}
|
|
tokens.push(text.slice(start, i));
|
|
}
|
|
const parts: string[] = [];
|
|
let depth = 0;
|
|
let length = 0;
|
|
const line = () => "\n" + " ".repeat(Math.min(depth, 100));
|
|
for (let i = 0; i < tokens.length; i++) {
|
|
const token = tokens[i]!;
|
|
let part = token;
|
|
if (token === "{" || token === "[") {
|
|
depth++;
|
|
if (tokens[i + 1] !== (token === "{" ? "}" : "]")) part += line();
|
|
} else if (token === "}" || token === "]") {
|
|
depth--;
|
|
if (tokens[i - 1] !== (token === "}" ? "{" : "[")) part = line() + token;
|
|
} else if (token === ",") part += line();
|
|
else if (token === ":") part += " ";
|
|
length += part.length;
|
|
if (length > 16_000_000) return text;
|
|
parts.push(part);
|
|
}
|
|
return parts.join("");
|
|
}
|
|
|
|
export function initialJsonText(text: string): string {
|
|
return text.length <= JSON_INLINE_LIMIT ? formatJson(text) : text;
|
|
}
|