-
Notifications
You must be signed in to change notification settings - Fork 11
/
ConfigJsonSchemaProvider.ts
176 lines (162 loc) · 5.97 KB
/
ConfigJsonSchemaProvider.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
import * as vscode from "vscode";
import { Logger } from "../logger";
import { RacyCacheTextDownloader, TextDownloader } from "../utils";
import { FolderInfos } from "./WorkspaceService";
/** Provides the dprint configuration JSON schema to vscode. */
export class ConfigJsonSchemaProvider implements vscode.TextDocumentContentProvider, vscode.Disposable {
#folderEditorInfos: FolderInfos | undefined;
#jsonSchemaUri = vscode.Uri.parse("dprint://schemas/config.json");
#logger: Logger;
#onDidChangeEmitter = new vscode.EventEmitter<vscode.Uri>();
#cachedTextDownloader: TextDownloader;
get onDidChange() {
return this.#onDidChangeEmitter.event;
}
constructor(logger: Logger, textDownloader: TextDownloader) {
this.#logger = logger;
this.#cachedTextDownloader = new RacyCacheTextDownloader(textDownloader);
}
static scheme = "dprint";
dispose() {
this.#onDidChangeEmitter.dispose();
}
setFolderInfos(infos: FolderInfos | undefined) {
this.#folderEditorInfos = infos;
// always refresh to reduce complexity (it's cheap to refresh)
this.#onDidChangeEmitter.fire(this.#jsonSchemaUri);
}
async provideTextDocumentContent(uri: vscode.Uri, _token: vscode.CancellationToken) {
if (uri.toString() !== this.#jsonSchemaUri.toString()) {
this.#logger.logWarn("Unknown JSON schema uri:", uri.toString());
return undefined;
}
const folderEditorInfos = this.#folderEditorInfos;
const configSchema = await this.#getRawConfigSchema(folderEditorInfos);
configSchema["$id"] = this.#jsonSchemaUri.toString();
if (folderEditorInfos != null) {
configSchema.properties = configSchema.properties ?? {};
// compromise: between workspace folders, the same plugin might appear
// with a different version. We compromise by selecting the first plugin
// found to be the one used, but perhaps an improvement would be to use
// the latest plugin version found. This would be a bit more complex to
// figure out though.
for (const { editorInfo: info } of folderEditorInfos) {
for (const plugin of info.plugins) {
if (plugin.configSchemaUrl != null && configSchema.properties[plugin.configKey] == null) {
configSchema.properties[plugin.configKey] = {
"$ref": plugin.configSchemaUrl,
};
}
}
}
}
return formatAsJson(configSchema);
}
async #getRawConfigSchema(folderEditorInfos: FolderInfos | undefined) {
// compromise: settle for the first one though they'll likely always be the same
const configSchemaUrl = folderEditorInfos?.[0]?.editorInfo?.configSchemaUrl;
if (configSchemaUrl == null) {
// provide a default schema while it hasn't loaded
return this.#getDefaultSchemaObject();
}
try {
this.#logger.logDebug("Fetching JSON schema:", configSchemaUrl);
const text = await this.#cachedTextDownloader.get(configSchemaUrl);
return JSON.parse(text);
} catch (err) {
this.#logger.logError("Error downloading config schema. Defaulting to built in schema.", err);
return this.#getDefaultSchemaObject();
}
}
#getDefaultSchemaObject() {
return {
$schema: "http://json-schema.org/draft-07/schema#",
$id: this.#jsonSchemaUri.toString(),
title: "dprint configuration file",
description: "Schema for a dprint configuration file.",
type: "object",
properties: {
incremental: {
description:
"Whether to format files only when they change. Setting this to `true` will dramatically speed up formatting.",
type: "boolean",
default: false,
},
extends: {
description: "Configurations to extend.",
anyOf: [{
description: "A file path or url to a configuration file to extend.",
type: "string",
}, {
description: "A collection of file paths and/or urls to configuration files to extend.",
type: "array",
items: {
type: "string",
},
}],
},
lineWidth: {
description:
"The width of a line the printer will try to stay under. Note that the printer may exceed this width in certain cases.",
type: "number",
default: 120,
},
indentWidth: {
description: "The number of characters for an indent.",
type: "number",
default: 4,
},
useTabs: {
description: "Whether to use tabs (true) or spaces (false) for indentation.",
type: "boolean",
default: false,
},
newLineKind: {
description: "The kind of newline to use.",
type: "string",
oneOf: [{
const: "auto",
description: "For each file, uses the newline kind found at the end of the last line.",
}, {
const: "crlf",
description: "Uses carriage return, line feed.",
}, {
const: "lf",
description: "Uses line feed.",
}, {
const: "system",
description: "Uses the system standard (ex. crlf on Windows).",
}],
},
includes: {
description: "Array of patterns (globs) to use to find files to format.",
type: "array",
items: {
type: "string",
},
},
excludes: {
description: "Array of patterns (globs) to exclude files or directories to format.",
type: "array",
items: {
type: "string",
},
},
plugins: {
description: "Array of plugin URLs to format files.",
type: "array",
items: {
type: "string",
},
},
},
additionalProperties: {
description: "Plugin configuration.",
type: "object",
},
};
}
}
function formatAsJson(data: object) {
return JSON.stringify(data, undefined, 2).replace(/\r?\n/, "\n");
}