compress schema

This commit is contained in:
Martin Aeschlimann 2024-06-25 11:24:22 +02:00
parent 94c4337eb7
commit cfd03950a8
No known key found for this signature in database
GPG Key ID: 4B615CF8ED6ADC96
2 changed files with 258 additions and 16 deletions

View File

@ -8,7 +8,8 @@ export type JSONLanguageStatus = { schemas: string[] };
import {
workspace, window, languages, commands, LogOutputChannel, ExtensionContext, extensions, Uri, ColorInformation,
Diagnostic, StatusBarAlignment, TextEditor, TextDocument, FormattingOptions, CancellationToken, FoldingRange,
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n
ProviderResult, TextEdit, Range, Position, Disposable, CompletionItem, CompletionList, CompletionContext, Hover, MarkdownString, FoldingContext, DocumentSymbol, SymbolInformation, l10n,
ViewColumn
} from 'vscode';
import {
LanguageClientOptions, RequestType, NotificationType, FormattingOptions as LSPFormattingOptions, DocumentDiagnosticReportKind,
@ -368,6 +369,8 @@ async function startClientWithParticipants(context: ExtensionContext, languagePa
if (uri.scheme !== 'http' && uri.scheme !== 'https') {
return workspace.openTextDocument(uri).then(doc => {
schemaDocuments[uri.toString()] = true;
runtime.logOutputChannel.info(`Loaded ${uri.toString()}, size ${doc.getText().length} characters`);
window.showTextDocument(doc, ViewColumn.Beside);
return doc.getText();
}, error => {
return Promise.reject(new ResponseError(2, error.toString()));

View File

@ -24,6 +24,9 @@ import { RegisteredEditorPriority, IEditorResolverService } from 'vs/workbench/s
import { ITextEditorService } from 'vs/workbench/services/textfile/common/textEditorService';
import { DEFAULT_SETTINGS_EDITOR_SETTING, FOLDER_SETTINGS_PATH, IPreferencesService, USE_SPLIT_JSON_SETTING } from 'vs/workbench/services/preferences/common/preferences';
import { IUserDataProfileService } from 'vs/workbench/services/userDataProfile/common/userDataProfile';
import { IJSONSchema, IJSONSchemaMap } from 'vs/base/common/jsonSchema';
import { JSONSchema } from 'vscode';
import { Lazy } from 'vs/base/common/lazy';
const schemaRegistry = Registry.as<JSONContributionRegistry.IJSONContributionRegistry>(JSONContributionRegistry.Extensions.JSONContribution);
@ -119,27 +122,263 @@ export class PreferencesContribution implements IWorkbenchContribution {
}
private getSchemaModel(uri: URI): ITextModel {
let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()] ?? {} /* Use empty schema if not yet registered */;
const modelContent = JSON.stringify(schema);
const languageSelection = this.languageService.createById('jsonc');
const model = this.modelService.createModel(modelContent, languageSelection, uri);
const disposables = new DisposableStore();
disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => {
if (schemaUri === uri.toString()) {
schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()];
model.setValue(JSON.stringify(schema));
const definitions = new Map<string, JSONSchema>();
class Definition {
constructor(public name: string, public schema: JSONSchema) {
}
}));
disposables.add(model.onWillDispose(() => disposables.dispose()));
return model;
}
class Cache {
nodesForProperty: JSONSchema[] = [];
nodesByString = new Lazy<Map<JSONSchema, string>>(() => {
const nodeToString = new Map<JSONSchema, Definition>();
for (const node of this.nodesForProperty) {
const stringToDefinition = new Map<string, Definition>();
const str = JSON.stringify(node);
const otherNode = stringToNode.get(str);
if (otherNode) {
nodeToString.set(otherNode, str);
nodeToString.set(node, str);
} else {
stringToNode.set(str, node);
}
}
return nodeToString;
}
}
const getModelContent = (schema: IJSONSchema) => {
const start = new Date();
const nodeByProperty = new Map<string, IJSONSchema[]>();
const groupByProperty = (property: string, next: IJSONSchema) => {
let nodes = nodeByProperty.get(property);
if (!nodes) {
nodes = [];
nodeByProperty.set(property, nodes);
}
nodes.push(next);
return true;
};
traverseNodes(schema, groupByProperty);
const defByString = new Map<string, Definition>();
const nodeToDefinition = new Map<string, Definition>();
for (const [property, nodes] of nodeByProperty) {
if (nodes.length > 1) {
for (const node of nodes) {
const str = JSON.stringify(node);
let def = defByString.get(str);
if (def) {
}
}
}
}
const nodeByString = new Map<string, IJSONSchema[]>();
const externalize = (property: string, next: IJSONSchema) => {
let nodes = nodeByProperty.get(property);
if (!nodes || nodes.length < 2) {
// don't externalize if there is only one node at this property
return true;
}
let nextStr = undefined;
for (const node of nodes) {
const str = JSON.stringify(node);
if (node === next) {
nextStr = str;
}
let nodes = nodeByString.get(str);
if (!nodes) {
nodes = [];
nodeByString.set(property, nodes);
}
nodes.push(node);
}
if (nodeByString.get(nextStr!)!.length > 1) {
}
if (!nodes) {
nodes = [];
nodeByProperty.set(property, nodes);
}
nodes.push(next);
return true;
};
}
const groupByProperty = (property: string, next: IJSONSchema) => {
let nodes = nodeByProperty.get(property);
if (!nodes) {
nodes = [];
nodeByProperty.set(property, nodes);
}
nodes.push(next);
return true;
};
for (const [property, nodes] of nodeByProperty) {
if (nodes.length > 1) {
const nodeBy
console.log(`Found duplicate property ${property} in schema ${uri}:`);
for (const node of nodes) {
console.log(JSON.stringify(node));
}
}
}
console.log(`Found ${nDups} duplicates in schema ${uri}. Took ${new Date().getTime() - start.getTime()}ms.`);
return JSON.stringify(schema);
};
let schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()] ?? {} /* Use empty schema if not yet registered */;
const modelContent = getModelContent(schema);
const languageSelection = this.languageService.createById('jsonc');
const model = this.modelService.createModel(modelContent, languageSelection, uri);
const disposables = new DisposableStore();
disposables.add(schemaRegistry.onDidChangeSchema(schemaUri => {
if (schemaUri === uri.toString()) {
schema = schemaRegistry.getSchemaContributions().schemas[uri.toString()];
model.setValue(getModelContent(schema));
}
}));
disposables.add(model.onWillDispose(() => disposables.dispose()));
return model;
}
dispose(): void {
dispose(this.editorOpeningListener);
dispose(this.settingsListener);
dispose(): void {
dispose(this.editorOpeningListener);
dispose(this.settingsListener);
}
}
type IJSONSchemaRef = IJSONSchema | boolean;
export function isObject(val: any): val is object {
return typeof val === 'object' && val !== null && !Array.isArray(val);
}
function traverseNodes(root: IJSONSchema, visit: (property: string, schema: IJSONSchema) => boolean) {
if (!root || typeof root !== 'object') {
return;
}
const collectEntries = (...entries: (IJSONSchemaRef | undefined)[]) => {
for (const entry of entries) {
if (isObject(entry)) {
toWalk.push(entry);
}
}
};
const collectMapEntries = (...maps: (IJSONSchemaMap | undefined)[]) => {
for (const map of maps) {
if (isObject(map)) {
for (const key in map) {
const entry = map[key];
if (isObject(entry)) {
toWalk.push([key, entry]);
}
}
}
}
};
const collectArrayEntries = (...arrays: (IJSONSchemaRef[] | undefined)[]) => {
for (const array of arrays) {
if (Array.isArray(array)) {
for (const entry of array) {
if (isObject(entry)) {
toWalk.push(entry);
}
}
}
}
};
const collectEntryOrArrayEntries = (items: (IJSONSchemaRef[] | IJSONSchemaRef | undefined)) => {
if (Array.isArray(items)) {
for (const entry of items) {
if (isObject(entry)) {
toWalk.push(entry);
}
}
} else if (isObject(items)) {
toWalk.push(items);
}
};
const toWalk: (IJSONSchema | [string, IJSONSchema])[] = [root];
let next = toWalk.pop();
while (next) {
let visitChildern = true;
if (Array.isArray(next)) {
visitChildern = visit(next[0], next[1]);
next = next[1];
}
if (visitChildern) {
collectEntries(next.additionalItems, next.additionalProperties, next.not, next.contains, next.propertyNames, next.if, next.then, next.else, next.unevaluatedItems, next.unevaluatedProperties);
collectMapEntries(next.definitions, next.$defs, next.properties, next.patternProperties, <IJSONSchemaMap>next.dependencies, next.dependentSchemas);
collectArrayEntries(next.anyOf, next.allOf, next.oneOf, next.prefixItems);
collectEntryOrArrayEntries(next.items);
}
next = toWalk.pop();
}
}
// Remove the unused function declaration
// function toStringWithReuse(schema: IJSONSchema) {
// const map = new Map<string, string>();
// function visit(schema: IJSONSchema) {
// if (schema.properties) {
// for (const key in schema.properties) {
// visit(schema.properties[key]);
// }
// } else {
// const str = JSON.stringify(schema);
// hash()
// }
// const key = JSON.stringify(schema);
// if (map.has(key)) {
// return map.get(key);
// }
// const result = doVisit(schema);
// map.set(key, result);
// return result;
// }
// if (schema.properties) {
// }
// }
const registry = Registry.as<IConfigurationRegistry>(Extensions.Configuration);
registry.registerConfiguration({
...workbenchConfigurationNodeBase,