@codingame/monaco-vscode-extensions-service-override
Version:
VSCode public API plugged on the monaco editor - extensions service-override
305 lines (301 loc) • 13.9 kB
JavaScript
import { __decorate, __param } from '@codingame/monaco-vscode-api/external/tslib/tslib.es6';
import { localize } from '@codingame/monaco-vscode-api/vscode/vs/nls';
import { ExtensionsRegistry } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensionsRegistry';
import { isProposedApiEnabled } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/extensions/common/extensions';
import { joinPath, isEqualOrParent } from '@codingame/monaco-vscode-api/vscode/vs/base/common/resources';
import { FileChangeType } from '@codingame/monaco-vscode-api/vscode/vs/platform/files/common/files';
import { IFileService } from '@codingame/monaco-vscode-api/vscode/vs/platform/files/common/files.service';
import { IBrowserWorkbenchEnvironmentService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/environment/browser/environmentService.service';
import { DisposableStore, toDisposable } from '@codingame/monaco-vscode-api/vscode/vs/base/common/lifecycle';
import { URI } from '@codingame/monaco-vscode-api/vscode/vs/base/common/uri';
import { FileAccess } from '@codingame/monaco-vscode-api/vscode/vs/base/common/network';
import { createLinkElement } from '@codingame/monaco-vscode-api/vscode/vs/base/browser/dom';
import { IWorkbenchThemeService } from '@codingame/monaco-vscode-api/vscode/vs/workbench/services/themes/common/workbenchThemeService.service';
import { StorageScope, StorageTarget } from '@codingame/monaco-vscode-api/vscode/vs/platform/storage/common/storage';
import { IStorageService } from '@codingame/monaco-vscode-api/vscode/vs/platform/storage/common/storage.service';
import { ExtensionIdentifier } from '@codingame/monaco-vscode-api/vscode/vs/platform/extensions/common/extensions';
import { registerWorkbenchContribution2, WorkbenchPhase } from '@codingame/monaco-vscode-api/vscode/vs/workbench/common/contributions';
import { IInstantiationService } from '@codingame/monaco-vscode-api/vscode/vs/platform/instantiation/common/instantiation';
const CSS_CACHE_STORAGE_KEY = "workbench.contrib.css.cache";
const cssExtensionPoint = ExtensionsRegistry.registerExtensionPoint({
extensionPoint: "css",
jsonSchema: {
description: ( localize(16957, "Contributes CSS files to be loaded in the workbench.")),
type: "array",
items: {
type: "object",
properties: {
path: {
description: ( localize(
16958,
"Path to the CSS file. The path is relative to the extension folder."
)),
type: "string"
}
},
required: ["path"]
},
defaultSnippets: [{
body: [{
path: "${1:styles.css}"
}]
}]
}
});
class CSSFileWatcher {
constructor(fileService, environmentService, onUpdate) {
this.fileService = fileService;
this.environmentService = environmentService;
this.onUpdate = onUpdate;
this.watchedLocations = ( new Map());
}
watch(uri) {
const key = ( uri.toString());
if (( this.watchedLocations.has(key))) {
return;
}
if (!this.environmentService.isExtensionDevelopment) {
return;
}
const disposables = ( new DisposableStore());
disposables.add(this.fileService.watch(uri));
disposables.add(this.fileService.onDidFilesChange(e => {
if (e.contains(uri, FileChangeType.UPDATED)) {
this.onUpdate(uri);
}
}));
this.watchedLocations.set(key, {
uri,
disposables
});
}
unwatch(uri) {
const key = ( uri.toString());
const entry = this.watchedLocations.get(key);
if (entry) {
entry.disposables.dispose();
this.watchedLocations.delete(key);
}
}
dispose() {
for (const entry of ( this.watchedLocations.values())) {
entry.disposables.dispose();
}
this.watchedLocations.clear();
}
}
let CSSExtensionPoint = class CSSExtensionPoint {
constructor(fileService, environmentService, themeService, storageService) {
this.themeService = themeService;
this.storageService = storageService;
this.disposables = ( new DisposableStore());
this.stylesheetsByExtension = ( new Map());
this.pendingExtensions = ( new Map());
this.watcher = this.disposables.add(( new CSSFileWatcher(fileService, environmentService, uri => this.reloadStylesheet(uri))));
this.disposables.add(toDisposable(() => {
for (const entries of ( this.stylesheetsByExtension.values())) {
for (const entry of entries) {
entry.disposables.dispose();
}
}
this.stylesheetsByExtension.clear();
}));
this.applyCachedCSS();
this.disposables.add(this.themeService.onDidColorThemeChange(() => this.onThemeChange()));
this.disposables.add(this.themeService.onDidFileIconThemeChange(() => this.onThemeChange()));
this.disposables.add(this.themeService.onDidProductIconThemeChange(() => this.onThemeChange()));
cssExtensionPoint.setHandler((extensions, delta) => {
for (const extension of delta.removed) {
const extensionId = extension.description.identifier.value;
this.pendingExtensions.delete(extensionId);
this.removeStylesheets(extensionId);
this.clearCacheForExtension(extensionId);
}
for (const extension of delta.added) {
if (!isProposedApiEnabled(extension.description)) {
extension.collector.error(`The '${cssExtensionPoint.name}' contribution point is proposed API.`);
continue;
}
const extensionValue = extension.value;
const collector = extension.collector;
if (!extensionValue || !Array.isArray(extensionValue)) {
collector.error(( localize(16959, "'contributes.css' must be an array.")));
continue;
}
const extensionId = extension.description.identifier.value;
this.pendingExtensions.set(extensionId, extension);
if (this.isExtensionThemeActive(extensionId)) {
this.activateExtensionCSS(extension);
} else if (( this.stylesheetsByExtension.has(extensionId))) {
this.removeStylesheets(extensionId);
this.clearCacheForExtension(extensionId);
}
}
});
}
isExtensionThemeActive(extensionId) {
const colorTheme = this.themeService.getColorTheme();
const fileIconTheme = this.themeService.getFileIconTheme();
const productIconTheme = this.themeService.getProductIconTheme();
return !!(colorTheme.extensionData && ExtensionIdentifier.equals(colorTheme.extensionData.extensionId, extensionId)) || !!(fileIconTheme.extensionData && ExtensionIdentifier.equals(fileIconTheme.extensionData.extensionId, extensionId)) || !!(productIconTheme.extensionData && ExtensionIdentifier.equals(productIconTheme.extensionData.extensionId, extensionId));
}
onThemeChange() {
for (const [extensionId, extension] of this.pendingExtensions) {
if (!( this.stylesheetsByExtension.has(extensionId)) && this.isExtensionThemeActive(extensionId)) {
this.activateExtensionCSS(extension);
}
}
for (const extensionId of ( this.stylesheetsByExtension.keys())) {
if (!this.isExtensionThemeActive(extensionId)) {
this.removeStylesheets(extensionId);
this.clearCacheForExtension(extensionId);
}
}
}
activateExtensionCSS(extension) {
const extensionId = extension.description.identifier.value;
if (( this.stylesheetsByExtension.has(extensionId))) {
return;
}
const extensionLocation = extension.description.extensionLocation;
const extensionValue = extension.value;
const collector = extension.collector;
const entries = [];
const cssLocations = [];
for (const cssContribution of extensionValue) {
if (!cssContribution.path || typeof cssContribution.path !== "string") {
collector.error(( localize(16960, "'contributes.css.path' must be a string.")));
continue;
}
const cssLocation = joinPath(extensionLocation, cssContribution.path);
if (!isEqualOrParent(cssLocation, extensionLocation)) {
collector.warn(( localize(
16961,
"Expected 'contributes.css.path' ({0}) to be included inside extension's folder ({1}).",
cssLocation.path,
extensionLocation.path
)));
continue;
}
const entryDisposables = ( new DisposableStore());
const element = this.createCSSLinkElement(cssLocation, extensionId, entryDisposables);
entries.push({
uri: cssLocation,
element,
disposables: entryDisposables
});
cssLocations.push(( cssLocation.toString()));
this.watcher.watch(cssLocation);
}
if (entries.length > 0) {
this.stylesheetsByExtension.set(extensionId, entries);
this.cacheExtensionCSS(extensionId, cssLocations);
}
}
removeStylesheets(extensionId) {
const entries = this.stylesheetsByExtension.get(extensionId);
if (entries) {
for (const entry of entries) {
this.watcher.unwatch(entry.uri);
entry.disposables.dispose();
}
this.stylesheetsByExtension.delete(extensionId);
}
}
applyCachedCSS() {
const cached = this.getCachedCSS();
if (!cached) {
return;
}
if (!this.isExtensionThemeActive(cached.extensionId)) {
this.clearCacheForExtension(cached.extensionId);
return;
}
const entries = [];
for (const cssLocationString of cached.cssLocations) {
const cssLocation = ( URI.parse(cssLocationString));
const entryDisposables = ( new DisposableStore());
const element = this.createCSSLinkElement(cssLocation, cached.extensionId, entryDisposables);
entries.push({
uri: cssLocation,
element,
disposables: entryDisposables
});
this.watcher.watch(cssLocation);
}
if (entries.length > 0) {
this.stylesheetsByExtension.set(cached.extensionId, entries);
}
}
getCachedCSS() {
const raw = this.storageService.get(CSS_CACHE_STORAGE_KEY, StorageScope.PROFILE);
if (!raw) {
return undefined;
}
try {
return JSON.parse(raw);
} catch {
return undefined;
}
}
cacheExtensionCSS(extensionId, cssLocations) {
const entry = {
extensionId,
cssLocations
};
this.storageService.store(
CSS_CACHE_STORAGE_KEY,
JSON.stringify(entry),
StorageScope.PROFILE,
StorageTarget.MACHINE
);
}
clearCacheForExtension(extensionId) {
const cached = this.getCachedCSS();
if (cached && ExtensionIdentifier.equals(cached.extensionId, extensionId)) {
this.storageService.remove(CSS_CACHE_STORAGE_KEY, StorageScope.PROFILE);
}
}
createCSSLinkElement(uri, extensionId, disposables) {
const element = createLinkElement();
element.rel = "stylesheet";
element.type = "text/css";
element.className = `extension-contributed-css ${extensionId}`;
element.href = ( FileAccess.uriToBrowserUri(uri).toString(true));
disposables.add(toDisposable(() => element.remove()));
return element;
}
reloadStylesheet(uri) {
const uriString = ( uri.toString());
for (const entries of ( this.stylesheetsByExtension.values())) {
for (const entry of entries) {
if (( entry.uri.toString()) === uriString) {
const browserUri = FileAccess.uriToBrowserUri(uri);
entry.element.href = ( browserUri.with({
query: `v=${Date.now()}`
}).toString(true));
}
}
}
}
dispose() {
this.disposables.dispose();
}
};
CSSExtensionPoint = ( __decorate([( __param(0, IFileService)), ( __param(1, IBrowserWorkbenchEnvironmentService)), ( __param(2, IWorkbenchThemeService)), ( __param(3, IStorageService))], CSSExtensionPoint));
let TokenClassificationExtensionPointWorkbenchContribution = class TokenClassificationExtensionPointWorkbenchContribution {
static {
this.ID = "workbench.contrib.cssExtensionPoint";
}
constructor(instantiationService) {
this.instantiationService = instantiationService;
this.instantiationService.createInstance(CSSExtensionPoint);
}
};
TokenClassificationExtensionPointWorkbenchContribution = ( __decorate([( __param(0, IInstantiationService))], TokenClassificationExtensionPointWorkbenchContribution));
registerWorkbenchContribution2(
TokenClassificationExtensionPointWorkbenchContribution.ID,
TokenClassificationExtensionPointWorkbenchContribution,
WorkbenchPhase.BlockStartup
);
export { CSSExtensionPoint };