UNPKG

monaco-editor-core

Version:

A browser based code editor

191 lines (190 loc) • 8.56 kB
/*--------------------------------------------------------------------------------------------- * Copyright (c) Microsoft Corporation. All rights reserved. * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ import { parse } from '../../../base/common/glob.js'; import { Mimes } from '../../../base/common/mime.js'; import { Schemas } from '../../../base/common/network.js'; import { basename, posix } from '../../../base/common/path.js'; import { DataUri } from '../../../base/common/resources.js'; import { startsWithUTF8BOM } from '../../../base/common/strings.js'; import { PLAINTEXT_LANGUAGE_ID } from '../languages/modesRegistry.js'; let registeredAssociations = []; let nonUserRegisteredAssociations = []; let userRegisteredAssociations = []; /** * Associate a language to the registry (platform). * * **NOTE**: This association will lose over associations registered using `registerConfiguredLanguageAssociation`. * * **NOTE**: Use `clearPlatformLanguageAssociations` to remove all associations registered using this function. */ export function registerPlatformLanguageAssociation(association, warnOnOverwrite = false) { _registerLanguageAssociation(association, false, warnOnOverwrite); } function _registerLanguageAssociation(association, userConfigured, warnOnOverwrite) { // Register const associationItem = toLanguageAssociationItem(association, userConfigured); registeredAssociations.push(associationItem); if (!associationItem.userConfigured) { nonUserRegisteredAssociations.push(associationItem); } else { userRegisteredAssociations.push(associationItem); } // Check for conflicts unless this is a user configured association if (warnOnOverwrite && !associationItem.userConfigured) { registeredAssociations.forEach(a => { if (a.mime === associationItem.mime || a.userConfigured) { return; // same mime or userConfigured is ok } if (associationItem.extension && a.extension === associationItem.extension) { console.warn(`Overwriting extension <<${associationItem.extension}>> to now point to mime <<${associationItem.mime}>>`); } if (associationItem.filename && a.filename === associationItem.filename) { console.warn(`Overwriting filename <<${associationItem.filename}>> to now point to mime <<${associationItem.mime}>>`); } if (associationItem.filepattern && a.filepattern === associationItem.filepattern) { console.warn(`Overwriting filepattern <<${associationItem.filepattern}>> to now point to mime <<${associationItem.mime}>>`); } if (associationItem.firstline && a.firstline === associationItem.firstline) { console.warn(`Overwriting firstline <<${associationItem.firstline}>> to now point to mime <<${associationItem.mime}>>`); } }); } } function toLanguageAssociationItem(association, userConfigured) { return { id: association.id, mime: association.mime, filename: association.filename, extension: association.extension, filepattern: association.filepattern, firstline: association.firstline, userConfigured: userConfigured, filenameLowercase: association.filename ? association.filename.toLowerCase() : undefined, extensionLowercase: association.extension ? association.extension.toLowerCase() : undefined, filepatternLowercase: association.filepattern ? parse(association.filepattern.toLowerCase()) : undefined, filepatternOnPath: association.filepattern ? association.filepattern.indexOf(posix.sep) >= 0 : false }; } /** * Clear language associations from the registry (platform). */ export function clearPlatformLanguageAssociations() { registeredAssociations = registeredAssociations.filter(a => a.userConfigured); nonUserRegisteredAssociations = []; } /** * @see `getMimeTypes` */ export function getLanguageIds(resource, firstLine) { return getAssociations(resource, firstLine).map(item => item.id); } function getAssociations(resource, firstLine) { let path; if (resource) { switch (resource.scheme) { case Schemas.file: path = resource.fsPath; break; case Schemas.data: { const metadata = DataUri.parseMetaData(resource); path = metadata.get(DataUri.META_DATA_LABEL); break; } case Schemas.vscodeNotebookCell: // File path not relevant for language detection of cell path = undefined; break; default: path = resource.path; } } if (!path) { return [{ id: 'unknown', mime: Mimes.unknown }]; } path = path.toLowerCase(); const filename = basename(path); // 1.) User configured mappings have highest priority const configuredLanguage = getAssociationByPath(path, filename, userRegisteredAssociations); if (configuredLanguage) { return [configuredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }]; } // 2.) Registered mappings have middle priority const registeredLanguage = getAssociationByPath(path, filename, nonUserRegisteredAssociations); if (registeredLanguage) { return [registeredLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }]; } // 3.) Firstline has lowest priority if (firstLine) { const firstlineLanguage = getAssociationByFirstline(firstLine); if (firstlineLanguage) { return [firstlineLanguage, { id: PLAINTEXT_LANGUAGE_ID, mime: Mimes.text }]; } } return [{ id: 'unknown', mime: Mimes.unknown }]; } function getAssociationByPath(path, filename, associations) { let filenameMatch = undefined; let patternMatch = undefined; let extensionMatch = undefined; // We want to prioritize associations based on the order they are registered so that the last registered // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074 for (let i = associations.length - 1; i >= 0; i--) { const association = associations[i]; // First exact name match if (filename === association.filenameLowercase) { filenameMatch = association; break; // take it! } // Longest pattern match if (association.filepattern) { if (!patternMatch || association.filepattern.length > patternMatch.filepattern.length) { const target = association.filepatternOnPath ? path : filename; // match on full path if pattern contains path separator if (association.filepatternLowercase?.(target)) { patternMatch = association; } } } // Longest extension match if (association.extension) { if (!extensionMatch || association.extension.length > extensionMatch.extension.length) { if (filename.endsWith(association.extensionLowercase)) { extensionMatch = association; } } } } // 1.) Exact name match has second highest priority if (filenameMatch) { return filenameMatch; } // 2.) Match on pattern if (patternMatch) { return patternMatch; } // 3.) Match on extension comes next if (extensionMatch) { return extensionMatch; } return undefined; } function getAssociationByFirstline(firstLine) { if (startsWithUTF8BOM(firstLine)) { firstLine = firstLine.substr(1); } if (firstLine.length > 0) { // We want to prioritize associations based on the order they are registered so that the last registered // association wins over all other. This is for https://github.com/microsoft/vscode/issues/20074 for (let i = registeredAssociations.length - 1; i >= 0; i--) { const association = registeredAssociations[i]; if (!association.firstline) { continue; } const matches = firstLine.match(association.firstline); if (matches && matches.length > 0) { return association; } } } return undefined; }