extensions/json-language-features/client/src/jsonClient.ts/handleContentChange
export function startClient(context: ExtensionContext, newLanguageClient: LanguageClientConstructor, runtime: Runtime) {

	const toDispose = context.subscriptions;

	let rangeFormatting: Disposable | undefined = undefined;


	const documentSelector = ['json', 'jsonc'];

	const schemaResolutionErrorStatusBarItem = window.createStatusBarItem('status.json.resolveError', StatusBarAlignment.Right, 0);
	schemaResolutionErrorStatusBarItem.name = localize('json.resolveError', "JSON: Schema Resolution Error");
	schemaResolutionErrorStatusBarItem.text = '$(alert)';
	toDispose.push(schemaResolutionErrorStatusBarItem);

	const fileSchemaErrors = new Map<string, string>();
	let schemaDownloadEnabled = true;

	// Options to control the language client
	const clientOptions: LanguageClientOptions = {
		// Register the server for json documents
		documentSelector,
		initializationOptions: {
			handledSchemaProtocols: ['file'], // language server only loads file-URI. Fetching schemas with other protocols ('http'...) are made on the client.
			provideFormatter: false, // tell the server to not provide formatting capability and ignore the `json.format.enable` setting.
			customCapabilities: { rangeFormatting: { editLimit: 10000 } }
		},
		synchronize: {
			// Synchronize the setting section 'json' to the server
			configurationSection: ['json', 'http'],
			fileEvents: workspace.createFileSystemWatcher('**/*.json')
		},
		middleware: {
			workspace: {
				didChangeConfiguration: () => client.sendNotification(DidChangeConfigurationNotification.type, { settings: getSettings() })
			},
			handleDiagnostics: (uri: Uri, diagnostics: Diagnostic[], next: HandleDiagnosticsSignature) => {
				const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError);

				if (schemaErrorIndex === -1) {
					fileSchemaErrors.delete(uri.toString());
					return next(uri, diagnostics);
				}

				const schemaResolveDiagnostic = diagnostics[schemaErrorIndex];
				fileSchemaErrors.set(uri.toString(), schemaResolveDiagnostic.message);

				if (!schemaDownloadEnabled) {
					diagnostics = diagnostics.filter(d => !isSchemaResolveError(d));
				}

				if (window.activeTextEditor && window.activeTextEditor.document.uri.toString() === uri.toString()) {
					schemaResolutionErrorStatusBarItem.show();
				}

				next(uri, diagnostics);
			},
			// testing the replace / insert mode
			provideCompletionItem(document: TextDocument, position: Position, context: CompletionContext, token: CancellationToken, next: ProvideCompletionItemsSignature): ProviderResult<CompletionItem[] | CompletionList> {
				function update(item: CompletionItem) {
					const range = item.range;
					if (range instanceof Range && range.end.isAfter(position) && range.start.isBeforeOrEqual(position)) {
						item.range = { inserting: new Range(range.start, position), replacing: range };
					}
					if (item.documentation instanceof MarkdownString) {
						item.documentation = updateMarkdownString(item.documentation);
					}

				}
				function updateProposals(r: CompletionItem[] | CompletionList | null | undefined): CompletionItem[] | CompletionList | null | undefined {
					if (r) {
						(Array.isArray(r) ? r : r.items).forEach(update);
					}
					return r;
				}

				const r = next(document, position, context, token);
				if (isThenable<CompletionItem[] | CompletionList | null | undefined>(r)) {
					return r.then(updateProposals);
				}
				return updateProposals(r);
			},
			provideHover(document: TextDocument, position: Position, token: CancellationToken, next: ProvideHoverSignature) {
				function updateHover(r: Hover | null | undefined): Hover | null | undefined {
					if (r && Array.isArray(r.contents)) {
						r.contents = r.contents.map(h => h instanceof MarkdownString ? updateMarkdownString(h) : h);
					}
					return r;
				}
				const r = next(document, position, token);
				if (isThenable<Hover | null | undefined>(r)) {
					return r.then(updateHover);
				}
				return updateHover(r);
			}
		}
	};

	// Create the language client and start the client.
	const client = newLanguageClient('json', localize('jsonserver.name', 'JSON Language Server'), clientOptions);
	client.registerProposedFeatures();

	const disposable = client.start();
	toDispose.push(disposable);
	client.onReady().then(() => {
		const schemaDocuments: { [uri: string]: boolean } = {};

		// handle content request
		client.onRequest(VSCodeContentRequest.type, (uriPath: string) => {
			const uri = Uri.parse(uriPath);
			if (uri.scheme === 'untitled') {
				return Promise.reject(new ResponseError(3, localize('untitled.schema', 'Unable to load {0}', uri.toString())));
			}
			if (uri.scheme !== 'http' && uri.scheme !== 'https') {
				return workspace.openTextDocument(uri).then(doc => {
					schemaDocuments[uri.toString()] = true;
					return doc.getText();
				}, error => {
					return Promise.reject(new ResponseError(2, error.toString()));
				});
			} else if (schemaDownloadEnabled) {
				if (runtime.telemetry && uri.authority === 'schema.management.azure.com') {
					/* __GDPR__
						"json.schema" : {
							"schemaURL" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
						}
					 */
					runtime.telemetry.sendTelemetryEvent('json.schema', { schemaURL: uriPath });
				}
				return runtime.http.getContent(uriPath);
			} else {
				return Promise.reject(new ResponseError(1, localize('schemaDownloadDisabled', 'Downloading schemas is disabled through setting \'{0}\'', SettingIds.enableSchemaDownload)));
			}
		});

		const handleContentChange = (uriString: string) => {
			if (schemaDocuments[uriString]) {
				client.sendNotification(SchemaContentChangeNotification.type, uriString);
				return true;
			}
			return false;
		};

		const handleActiveEditorChange = (activeEditor?: TextEditor) => {
			if (!activeEditor) {
				return;
			}

			const activeDocUri = activeEditor.document.uri.toString();

			if (activeDocUri && fileSchemaErrors.has(activeDocUri)) {
				schemaResolutionErrorStatusBarItem.show();
			} else {
				schemaResolutionErrorStatusBarItem.hide();
			}
		};

		toDispose.push(workspace.onDidChangeTextDocument(e => handleContentChange(e.document.uri.toString())));
		toDispose.push(workspace.onDidCloseTextDocument(d => {
			const uriString = d.uri.toString();
			if (handleContentChange(uriString)) {
				delete schemaDocuments[uriString];
			}
			fileSchemaErrors.delete(uriString);
		}));
		toDispose.push(window.onDidChangeActiveTextEditor(handleActiveEditorChange));

		const handleRetryResolveSchemaCommand = () => {
			if (window.activeTextEditor) {
				schemaResolutionErrorStatusBarItem.text = '$(watch)';
				const activeDocUri = window.activeTextEditor.document.uri.toString();
				client.sendRequest(ForceValidateRequest.type, activeDocUri).then((diagnostics) => {
					const schemaErrorIndex = diagnostics.findIndex(isSchemaResolveError);
					if (schemaErrorIndex !== -1) {
						// Show schema resolution errors in status bar only; ref: #51032
						const schemaResolveDiagnostic = diagnostics[schemaErrorIndex];
						fileSchemaErrors.set(activeDocUri, schemaResolveDiagnostic.message);
					} else {
						schemaResolutionErrorStatusBarItem.hide();
					}
					schemaResolutionErrorStatusBarItem.text = '$(alert)';
				});
			}
		};

		toDispose.push(commands.registerCommand('_json.retryResolveSchema', handleRetryResolveSchemaCommand));

		client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));

		extensions.onDidChange(_ => {
			client.sendNotification(SchemaAssociationNotification.type, getSchemaAssociations(context));
		});

		// manually register / deregister format provider based on the `json.format.enable` setting avoiding issues with late registration. See #71652.
		updateFormatterRegistration();
		toDispose.push({ dispose: () => rangeFormatting && rangeFormatting.dispose() });

		updateSchemaDownloadSetting();

		toDispose.push(workspace.onDidChangeConfiguration(e => {
			if (e.affectsConfiguration(SettingIds.enableFormatter)) {
				updateFormatterRegistration();
			} else if (e.affectsConfiguration(SettingIds.enableSchemaDownload)) {
				updateSchemaDownloadSetting();
			}
		}));

		client.onNotification(ResultLimitReachedNotification.type, async message => {
			const shouldPrompt = context.globalState.get<boolean>(StorageIds.maxItemsExceededInformation) !== false;
			if (shouldPrompt) {
				const ok = localize('ok', "Ok");
				const openSettings = localize('goToSetting', 'Open Settings');
				const neverAgain = localize('yes never again', "Don't Show Again");
				const pick = await window.showInformationMessage(`${message}\n${localize('configureLimit', 'Use setting \'{0}\' to configure the limit.', SettingIds.maxItemsComputed)}`, ok, openSettings, neverAgain);
				if (pick === neverAgain) {
					await context.globalState.update(StorageIds.maxItemsExceededInformation, false);
				} else if (pick === openSettings) {
					await commands.executeCommand('workbench.action.openSettings', SettingIds.maxItemsComputed);
				}
			}
		});

		function updateFormatterRegistration() {
			const formatEnabled = workspace.getConfiguration().get(SettingIds.enableFormatter);
			if (!formatEnabled && rangeFormatting) {
				rangeFormatting.dispose();
				rangeFormatting = undefined;
			} else if (formatEnabled && !rangeFormatting) {
				rangeFormatting = languages.registerDocumentRangeFormattingEditProvider(documentSelector, {
					provideDocumentRangeFormattingEdits(document: TextDocument, range: Range, options: FormattingOptions, token: CancellationToken): ProviderResult<TextEdit[]> {
						const filesConfig = workspace.getConfiguration('files', document);
						const fileFormattingOptions = {
							trimTrailingWhitespace: filesConfig.get<boolean>('trimTrailingWhitespace'),
							trimFinalNewlines: filesConfig.get<boolean>('trimFinalNewlines'),
							insertFinalNewline: filesConfig.get<boolean>('insertFinalNewline'),
						};
						const params: DocumentRangeFormattingParams = {
							textDocument: client.code2ProtocolConverter.asTextDocumentIdentifier(document),
							range: client.code2ProtocolConverter.asRange(range),
							options: client.code2ProtocolConverter.asFormattingOptions(options, fileFormattingOptions)
						};

						return client.sendRequest(DocumentRangeFormattingRequest.type, params, token).then(
							client.protocol2CodeConverter.asTextEdits,
							(error) => {
								client.handleFailedRequest(DocumentRangeFormattingRequest.type, error, []);
								return Promise.resolve([]);
							}
						);
					}
				});
			}
		}

		function updateSchemaDownloadSetting() {
			schemaDownloadEnabled = workspace.getConfiguration().get(SettingIds.enableSchemaDownload) !== false;
			if (schemaDownloadEnabled) {
				schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionErrorMessage', 'Unable to resolve schema. Click to retry.');
				schemaResolutionErrorStatusBarItem.command = '_json.retryResolveSchema';
				handleRetryResolveSchemaCommand();
			} else {
				schemaResolutionErrorStatusBarItem.tooltip = localize('json.schemaResolutionDisabledMessage', 'Downloading schemas is disabled. Click to configure.');
				schemaResolutionErrorStatusBarItem.command = { command: 'workbench.action.openSettings', arguments: [SettingIds.enableSchemaDownload], title: '' };
			}
		}

	});
}