提交 27870487 编写于 作者: B Benjamin Pasero

user configured mimes get highest priority

上级 932e8637
...@@ -13,140 +13,83 @@ export let MIME_TEXT = 'text/plain'; ...@@ -13,140 +13,83 @@ export let MIME_TEXT = 'text/plain';
export let MIME_BINARY = 'application/octet-stream'; export let MIME_BINARY = 'application/octet-stream';
export let MIME_UNKNOWN = 'application/unknown'; export let MIME_UNKNOWN = 'application/unknown';
export interface ITextMimeAssociation {
pattern?: string;
firstLineRegExp?: RegExp;
userConfigured?: boolean;
}
const registeredTextMimesByFilename: { [str: string]: string; } = Object.create(null); const registeredTextMimesByFilename: { [str: string]: string; } = Object.create(null);
const userConfiguredTextMimesByFilename: { [str: string]: string; } = Object.create(null);
const registeredTextMimesByFirstLine: { regexp: RegExp; mime: string; }[] = []; const registeredTextMimesByFirstLine: { regexp: RegExp; mime: string; }[] = [];
// This is for automatic generation at native.guplfile.js#41 => darwinBundleDocumentTypes.extensions /**
export function generateKnownFilenames(onlyExtensions: boolean = true): any { * Associate a text mime to the registry
let filter = (ext: string) => { */
if (onlyExtensions) { export function registerTextMime(mime: string, association: ITextMimeAssociation): void {
return /^\./.test(ext); if (mime && association) {
}
return true;
};
let removeLeadingDot = (ext: string) => {
return ext.replace(/^\./, '');
};
let list: string[] = [];
list = list.concat(Object.keys(registeredTextMimesByFilename));
list = list.filter(filter).map(removeLeadingDot);
list.sort();
let result: string[] = [];
let currentLetter: string = null;
let previousItem: string = null;
let currentRow: string[] = [];
let pushCurrentRow = () => {
if (currentRow.length > 0) {
result.push('\'' + currentRow.join('\', \'') + '\'');
}
};
for (let i = 0, len = list.length; i < len; i++) { // Firstline pattern
let item = list[i]; if (association.firstLineRegExp) {
if (item.length === 0) { registeredTextMimesByFirstLine.push({ regexp: association.firstLineRegExp, mime: mime });
continue;
} }
if (item === previousItem) {
continue;
}
let letter = item.charAt(0);
if (currentLetter !== letter) { // User configured
pushCurrentRow(); if (association.userConfigured && association.pattern) {
currentLetter = letter; userConfiguredTextMimesByFilename[association.pattern] = mime;
currentRow = [];
} }
currentRow.push(item); // Built in or via Extension
previousItem = item; else if (association.pattern) {
} if (registeredTextMimesByFilename[association.pattern] && registeredTextMimesByFilename[association.pattern] !== mime) {
pushCurrentRow(); console.warn('Overwriting filename <<' + association.pattern + '>> to now point to mime <<' + mime + '>>');
}
return result.join(',\n'); registeredTextMimesByFilename[association.pattern] = mime;
}
/**
* Allow to register extra text mimes dynamically based on filename
*/
export function registerTextMimeByFilename(nameOrPatternOrPrefix: string, mime: string): void {
if (nameOrPatternOrPrefix && mime) {
if (registeredTextMimesByFilename[nameOrPatternOrPrefix] && registeredTextMimesByFilename[nameOrPatternOrPrefix] !== mime) {
console.warn('Overwriting filename <<' + nameOrPatternOrPrefix + '>> to now point to mime <<' + mime + '>>');
} }
registeredTextMimesByFilename[nameOrPatternOrPrefix] = mime;
} }
} }
/** /**
* Allow to register extra text mimes dynamically based on firstline * Given a file, return the best matching mime type for it
*/
export function registerTextMimeByFirstLine(firstLineRegexp: RegExp, mime: string): void {
if (firstLineRegexp && mime) {
registeredTextMimesByFirstLine.push({ regexp: firstLineRegexp, mime: mime });
}
}
/**
* Given a comma separated list of mimes in order of priority, find if the list describes a binary
* or textual resource.
*/
export function isBinaryMime(mimes: string): boolean;
export function isBinaryMime(mimes: string[]): boolean;
export function isBinaryMime(mimes: any): boolean {
if (!mimes) {
return false;
}
let mimeVals: string[];
if (types.isArray(mimes)) {
mimeVals = (<string[]>mimes);
} else {
mimeVals = (<string>mimes).split(',').map((mime) => mime.trim());
}
return mimeVals.indexOf(MIME_BINARY) >= 0;
}
/**
* New function for mime type detection supporting application/unknown as concept.
*/ */
export function guessMimeTypes(path: string, firstLine?: string): string[] { export function guessMimeTypes(path: string, firstLine?: string): string[] {
if (!path) { if (!path) {
return [MIME_UNKNOWN]; return [MIME_UNKNOWN];
} }
// 1.) Firstline gets highest priority path = path.toLowerCase();
if (firstLine) { let filename = paths.basename(path);
if (strings.startsWithUTF8BOM(firstLine)) {
firstLine = firstLine.substr(1);
}
if (firstLine.length > 0) { // 1.) Configured mappings have highest priority
for (let i = 0; i < registeredTextMimesByFirstLine.length; ++i) { let configuredMime = guessMimeTypeByFilename(filename, userConfiguredTextMimesByFilename);
if (configuredMime) {
return [configuredMime, MIME_TEXT];
}
// Make sure the entire line matches, not just a subpart. // 2.) Firstline has high priority over registered mappings
let matches = firstLine.match(registeredTextMimesByFirstLine[i].regexp); if (firstLine) {
if (matches && matches.length > 0 && matches[0].length === firstLine.length) { let firstlineMime = guessMimeTypeByFirstline(firstLine);
return [registeredTextMimesByFirstLine[i].mime, MIME_TEXT]; if (firstlineMime) {
} return [firstlineMime, MIME_TEXT];
}
} }
} }
// Check with file name and extension // 3.) Registered mappings have lowest priority
path = path.toLowerCase(); let registeredMime = guessMimeTypeByFilename(filename, registeredTextMimesByFilename);
let filename = paths.basename(path); if (registeredMime) {
return [registeredMime, MIME_TEXT];
}
return [MIME_UNKNOWN];
}
function guessMimeTypeByFilename(filename: string, map: { [str: string]: string; }): string {
let exactNameMatch: string; let exactNameMatch: string;
let extensionMatch: string; let extensionMatch: string;
let patternNameMatch: string; let patternNameMatch: string;
// Check for dynamically registered match based on filename and extension // Check for dynamically registered match based on filename and extension
for (let nameOrPatternOrPrefix in registeredTextMimesByFilename) { for (let nameOrPatternOrPrefix in map) {
let nameOrPatternOrExtensionLower: string = nameOrPatternOrPrefix.toLowerCase(); let nameOrPatternOrExtensionLower: string = nameOrPatternOrPrefix.toLowerCase();
// First exact name match // First exact name match
...@@ -172,20 +115,56 @@ export function guessMimeTypes(path: string, firstLine?: string): string[] { ...@@ -172,20 +115,56 @@ export function guessMimeTypes(path: string, firstLine?: string): string[] {
// 2.) Exact name match has second highest prio // 2.) Exact name match has second highest prio
if (exactNameMatch) { if (exactNameMatch) {
return [registeredTextMimesByFilename[exactNameMatch], MIME_TEXT]; return map[exactNameMatch];
} }
// 3.) Match on pattern // 3.) Match on pattern
if (patternNameMatch) { if (patternNameMatch) {
return [registeredTextMimesByFilename[patternNameMatch], MIME_TEXT]; return map[patternNameMatch];
} }
// 4.) Match on extension comes next // 4.) Match on extension comes next
if (extensionMatch) { if (extensionMatch) {
return [registeredTextMimesByFilename[extensionMatch], MIME_TEXT]; return map[extensionMatch];
} }
return [MIME_UNKNOWN]; return null;
}
function guessMimeTypeByFirstline(firstLine: string): string {
if (strings.startsWithUTF8BOM(firstLine)) {
firstLine = firstLine.substr(1);
}
if (firstLine.length > 0) {
for (let i = 0; i < registeredTextMimesByFirstLine.length; ++i) {
// Make sure the entire line matches, not just a subpart.
let matches = firstLine.match(registeredTextMimesByFirstLine[i].regexp);
if (matches && matches.length > 0 && matches[0].length === firstLine.length) {
return registeredTextMimesByFirstLine[i].mime;
}
}
}
return null;
}
export function isBinaryMime(mimes: string): boolean;
export function isBinaryMime(mimes: string[]): boolean;
export function isBinaryMime(mimes: any): boolean {
if (!mimes) {
return false;
}
let mimeVals: string[];
if (types.isArray(mimes)) {
mimeVals = (<string[]>mimes);
} else {
mimeVals = (<string>mimes).split(',').map((mime) => mime.trim());
}
return mimeVals.indexOf(MIME_BINARY) >= 0;
} }
export function isUnspecific(mime: string[] | string): boolean { export function isUnspecific(mime: string[] | string): boolean {
......
...@@ -5,35 +5,39 @@ ...@@ -5,35 +5,39 @@
'use strict'; 'use strict';
import * as assert from 'assert'; import * as assert from 'assert';
import { guessMimeTypes, registerTextMimeByFilename, registerTextMimeByFirstLine } from 'vs/base/common/mime'; import { guessMimeTypes, registerTextMime } from 'vs/base/common/mime';
function register(pattern: string, mime: string, user?: boolean): void {
registerTextMime(mime, { pattern: pattern, userConfigured: user });
}
suite('Mime', () => { suite('Mime', () => {
test('Dynamically Register Text Mime', () => { test('Dynamically Register Text Mime', () => {
var guess = guessMimeTypes('foo.monaco'); var guess = guessMimeTypes('foo.monaco');
assert.deepEqual(guess, ['application/unknown']); assert.deepEqual(guess, ['application/unknown']);
registerTextMimeByFilename('.monaco', 'text/monaco'); register('.monaco', 'text/monaco');
guess = guessMimeTypes('foo.monaco'); guess = guessMimeTypes('foo.monaco');
assert.deepEqual(guess, ['text/monaco', 'text/plain']); assert.deepEqual(guess, ['text/monaco', 'text/plain']);
guess = guessMimeTypes('.monaco'); guess = guessMimeTypes('.monaco');
assert.deepEqual(guess, ['text/monaco', 'text/plain']); assert.deepEqual(guess, ['text/monaco', 'text/plain']);
registerTextMimeByFilename('Codefile', 'text/code'); register('Codefile', 'text/code');
guess = guessMimeTypes('Codefile'); guess = guessMimeTypes('Codefile');
assert.deepEqual(guess, ['text/code', 'text/plain']); assert.deepEqual(guess, ['text/code', 'text/plain']);
guess = guessMimeTypes('foo.Codefile'); guess = guessMimeTypes('foo.Codefile');
assert.deepEqual(guess, ['application/unknown']); assert.deepEqual(guess, ['application/unknown']);
registerTextMimeByFilename('Docker*', 'text/docker'); register('Docker*', 'text/docker');
guess = guessMimeTypes('Docker-debug'); guess = guessMimeTypes('Docker-debug');
assert.deepEqual(guess, ['text/docker', 'text/plain']); assert.deepEqual(guess, ['text/docker', 'text/plain']);
guess = guessMimeTypes('docker-PROD'); guess = guessMimeTypes('docker-PROD');
assert.deepEqual(guess, ['text/docker', 'text/plain']); assert.deepEqual(guess, ['text/docker', 'text/plain']);
registerTextMimeByFirstLine(/RegexesAreNice/, 'text/nice-regex'); registerTextMime('text/nice-regex', { firstLineRegExp: /RegexesAreNice/ });
guess = guessMimeTypes('Randomfile.noregistration', 'RegexesAreNice'); guess = guessMimeTypes('Randomfile.noregistration', 'RegexesAreNice');
assert.deepEqual(guess, ['text/nice-regex', 'text/plain']); assert.deepEqual(guess, ['text/nice-regex', 'text/plain']);
...@@ -45,8 +49,8 @@ suite('Mime', () => { ...@@ -45,8 +49,8 @@ suite('Mime', () => {
}); });
test('Mimes Priority', () => { test('Mimes Priority', () => {
registerTextMimeByFilename('.monaco', 'text/monaco'); register('.monaco', 'text/monaco');
registerTextMimeByFirstLine(/foobar/, 'text/foobar'); registerTextMime('text/foobar', { firstLineRegExp: /foobar/ });
var guess = guessMimeTypes('foo.monaco'); var guess = guessMimeTypes('foo.monaco');
assert.deepEqual(guess, ['text/monaco', 'text/plain']); assert.deepEqual(guess, ['text/monaco', 'text/plain']);
...@@ -54,32 +58,32 @@ suite('Mime', () => { ...@@ -54,32 +58,32 @@ suite('Mime', () => {
guess = guessMimeTypes('foo.monaco', 'foobar'); guess = guessMimeTypes('foo.monaco', 'foobar');
assert.deepEqual(guess, ['text/foobar', 'text/plain']); assert.deepEqual(guess, ['text/foobar', 'text/plain']);
registerTextMimeByFilename('dockerfile', 'text/winner'); register('dockerfile', 'text/winner');
registerTextMimeByFilename('dockerfile*', 'text/looser'); register('dockerfile*', 'text/looser');
guess = guessMimeTypes('dockerfile'); guess = guessMimeTypes('dockerfile');
assert.deepEqual(guess, ['text/winner', 'text/plain']); assert.deepEqual(guess, ['text/winner', 'text/plain']);
}); });
test('Specificity priority 1', () => { test('Specificity priority 1', () => {
registerTextMimeByFilename('.monaco2', 'text/monaco2'); register('.monaco2', 'text/monaco2');
registerTextMimeByFilename('specific.monaco2', 'text/specific-monaco2'); register('specific.monaco2', 'text/specific-monaco2');
assert.deepEqual(guessMimeTypes('specific.monaco2'), ['text/specific-monaco2', 'text/plain']); assert.deepEqual(guessMimeTypes('specific.monaco2'), ['text/specific-monaco2', 'text/plain']);
assert.deepEqual(guessMimeTypes('foo.monaco2'), ['text/monaco2', 'text/plain']); assert.deepEqual(guessMimeTypes('foo.monaco2'), ['text/monaco2', 'text/plain']);
}); });
test('Specificity priority 2', () => { test('Specificity priority 2', () => {
registerTextMimeByFilename('specific.monaco3', 'text/specific-monaco3'); register('specific.monaco3', 'text/specific-monaco3');
registerTextMimeByFilename('.monaco3', 'text/monaco3'); register('.monaco3', 'text/monaco3');
assert.deepEqual(guessMimeTypes('specific.monaco3'), ['text/specific-monaco3', 'text/plain']); assert.deepEqual(guessMimeTypes('specific.monaco3'), ['text/specific-monaco3', 'text/plain']);
assert.deepEqual(guessMimeTypes('foo.monaco3'), ['text/monaco3', 'text/plain']); assert.deepEqual(guessMimeTypes('foo.monaco3'), ['text/monaco3', 'text/plain']);
}); });
test('Mimes Priority - Longest Extension wins', () => { test('Mimes Priority - Longest Extension wins', () => {
registerTextMimeByFilename('.monaco', 'text/monaco'); register('.monaco', 'text/monaco');
registerTextMimeByFilename('.monaco.xml', 'text/monaco-xml'); register('.monaco.xml', 'text/monaco-xml');
registerTextMimeByFilename('.monaco.xml.build', 'text/monaco-xml-build'); register('.monaco.xml.build', 'text/monaco-xml-build');
var guess = guessMimeTypes('foo.monaco'); var guess = guessMimeTypes('foo.monaco');
assert.deepEqual(guess, ['text/monaco', 'text/plain']); assert.deepEqual(guess, ['text/monaco', 'text/plain']);
...@@ -90,4 +94,12 @@ suite('Mime', () => { ...@@ -90,4 +94,12 @@ suite('Mime', () => {
guess = guessMimeTypes('foo.monaco.xml.build'); guess = guessMimeTypes('foo.monaco.xml.build');
assert.deepEqual(guess, ['text/monaco-xml-build', 'text/plain']); assert.deepEqual(guess, ['text/monaco-xml-build', 'text/plain']);
}); });
test('Mimes Priority - User configured wins', () => {
register('.monaco.xml', 'text/monaco', true);
register('.monaco.xml', 'text/monaco-xml');
var guess = guessMimeTypes('foo.monaco.xml');
assert.deepEqual(guess, ['text/monaco', 'text/plain']);
});
}); });
...@@ -6,7 +6,6 @@ ...@@ -6,7 +6,6 @@
'use strict'; 'use strict';
import assert = require('assert'); import assert = require('assert');
import path = require('path');
import mimeCommon = require('vs/base/common/mime'); import mimeCommon = require('vs/base/common/mime');
import mime = require('vs/base/node/mime'); import mime = require('vs/base/node/mime');
...@@ -24,7 +23,7 @@ suite('Mime', () => { ...@@ -24,7 +23,7 @@ suite('Mime', () => {
}); });
test('detectMimesFromFile (PNG saved as TXT)', function(done: () => void) { test('detectMimesFromFile (PNG saved as TXT)', function(done: () => void) {
mimeCommon.registerTextMimeByFilename('.txt', 'text/plain'); mimeCommon.registerTextMime('text/plain', { pattern: '.txt' });
var file = require.toUrl('./fixtures/some.png.txt'); var file = require.toUrl('./fixtures/some.png.txt');
mime.detectMimesFromFile(file, (error, mimes) => { mime.detectMimesFromFile(file, (error, mimes) => {
assert.equal(error, null); assert.equal(error, null);
......
...@@ -103,19 +103,19 @@ export class LanguagesRegistry { ...@@ -103,19 +103,19 @@ export class LanguagesRegistry {
if (Array.isArray(lang.extensions)) { if (Array.isArray(lang.extensions)) {
for (let extension of lang.extensions) { for (let extension of lang.extensions) {
mime.registerTextMimeByFilename(extension, primaryMime); mime.registerTextMime(primaryMime, { pattern: extension });
} }
} }
if (Array.isArray(lang.filenames)) { if (Array.isArray(lang.filenames)) {
for (let filename of lang.filenames) { for (let filename of lang.filenames) {
mime.registerTextMimeByFilename(filename, primaryMime); mime.registerTextMime(primaryMime, { pattern: filename });
} }
} }
if (Array.isArray(lang.filenamePatterns)) { if (Array.isArray(lang.filenamePatterns)) {
for (let filenamePattern of lang.filenamePatterns) { for (let filenamePattern of lang.filenamePatterns) {
mime.registerTextMimeByFilename(filenamePattern, primaryMime); mime.registerTextMime(primaryMime, { pattern: filenamePattern });
} }
} }
...@@ -127,7 +127,7 @@ export class LanguagesRegistry { ...@@ -127,7 +127,7 @@ export class LanguagesRegistry {
try { try {
var firstLineRegex = new RegExp(firstLineRegexStr); var firstLineRegex = new RegExp(firstLineRegexStr);
if (!strings.regExpLeadsToEndlessLoop(firstLineRegex)) { if (!strings.regExpLeadsToEndlessLoop(firstLineRegex)) {
mime.registerTextMimeByFirstLine(firstLineRegex, primaryMime); mime.registerTextMime(primaryMime, { firstLineRegExp: firstLineRegex });
} }
} catch (err) { } catch (err) {
// Most likely, the regex was bad // Most likely, the regex was bad
......
...@@ -41,7 +41,7 @@ export class FileAssociations implements IWorkbenchContribution { ...@@ -41,7 +41,7 @@ export class FileAssociations implements IWorkbenchContribution {
private onConfigurationChange(configuration: IFilesConfiguration): void { private onConfigurationChange(configuration: IFilesConfiguration): void {
if (configuration.files && configuration.files.associations) { if (configuration.files && configuration.files.associations) {
Object.keys(configuration.files.associations).forEach(pattern => { Object.keys(configuration.files.associations).forEach(pattern => {
mime.registerTextMimeByFilename(pattern, this.modeService.getMimeForMode(configuration.files.associations[pattern])); mime.registerTextMime(this.modeService.getMimeForMode(configuration.files.associations[pattern]), { pattern: pattern, userConfigured: true });
}); });
} }
} }
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册