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

files - extract resource encoding related helper

上级 2717a0e9
......@@ -18,9 +18,18 @@ import { isUndefinedOrNull } from 'vs/base/common/types';
export const IFileService = createDecorator<IFileService>('fileService');
export interface IResourceEncodings {
getWriteEncoding(resource: URI, preferredEncoding?: string): string;
}
export interface IFileService {
_serviceBrand: any;
/**
* Helper to determine read/write encoding for resources.
*/
encoding: IResourceEncodings;
/**
* Allows to listen for file changes. The event will fire for every file within the opened workspace
* (if any) as well as all files that have been watched explicitly using the #watchFileChanges() API.
......@@ -135,11 +144,6 @@ export interface IFileService {
*/
unwatchFileChanges(resource: URI): void;
/**
* Returns the preferred encoding to use for a given resource.
*/
getEncoding(resource: URI, preferredEncoding?: string): string;
/**
* Frees up any resources occupied by this service.
*/
......
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the MIT License. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import * as encoding from 'vs/base/node/encoding';
import uri from 'vs/base/common/uri';
import { IResolveContentOptions, isParent, IResourceEncodings } from 'vs/platform/files/common/files';
import { isLinux } from 'vs/base/common/platform';
import { join, extname } from 'path';
import { ITextResourceConfigurationService } from 'vs/editor/common/services/resourceConfiguration';
import { IEnvironmentService } from 'vs/platform/environment/common/environment';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
export interface IEncodingOverride {
parent?: uri;
extension?: string;
encoding: string;
}
// TODO@Ben debt - encodings should move one layer up from the file service into the text file
// service and then ideally be passed in as option to the file service
// the file service should talk about string | Buffer for reading and writing and only convert
// to strings if a encoding is provided
export class ResourceEncodings implements IResourceEncodings {
private encodingOverride: IEncodingOverride[];
private toDispose: IDisposable[];
constructor(
private textResourceConfigurationService: ITextResourceConfigurationService,
private environmentService: IEnvironmentService,
private contextService: IWorkspaceContextService,
encodingOverride?: IEncodingOverride[]
) {
this.encodingOverride = encodingOverride || this.getEncodingOverrides();
this.toDispose = [];
this.registerListeners();
}
private registerListeners(): void {
// Workspace Folder Change
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => {
this.encodingOverride = this.getEncodingOverrides();
}));
}
public getReadEncoding(resource: uri, options: IResolveContentOptions, detected: encoding.IDetectedEncodingResult): string {
let preferredEncoding: string;
// Encoding passed in as option
if (options && options.encoding) {
if (detected.encoding === encoding.UTF8 && options.encoding === encoding.UTF8) {
preferredEncoding = encoding.UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8
} else {
preferredEncoding = options.encoding; // give passed in encoding highest priority
}
}
// Encoding detected
else if (detected.encoding) {
if (detected.encoding === encoding.UTF8) {
preferredEncoding = encoding.UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM
} else {
preferredEncoding = detected.encoding;
}
}
// Encoding configured
else if (this.textResourceConfigurationService.getValue(resource, 'files.encoding') === encoding.UTF8_with_bom) {
preferredEncoding = encoding.UTF8; // if we did not detect UTF 8 BOM before, this can only be UTF 8 then
}
return this.getEncodingForResource(resource, preferredEncoding);
}
public getWriteEncoding(resource: uri, preferredEncoding?: string): string {
return this.getEncodingForResource(resource, preferredEncoding);
}
private getEncodingForResource(resource: uri, preferredEncoding?: string): string {
let fileEncoding: string;
const override = this.getEncodingOverride(resource);
if (override) {
fileEncoding = override; // encoding override always wins
} else if (preferredEncoding) {
fileEncoding = preferredEncoding; // preferred encoding comes second
} else {
fileEncoding = this.textResourceConfigurationService.getValue(resource, 'files.encoding'); // and last we check for settings
}
if (!fileEncoding || !encoding.encodingExists(fileEncoding)) {
fileEncoding = encoding.UTF8; // the default is UTF 8
}
return fileEncoding;
}
private getEncodingOverrides(): IEncodingOverride[] {
const encodingOverride: IEncodingOverride[] = [];
// Global settings
encodingOverride.push({ parent: uri.file(this.environmentService.appSettingsHome), encoding: encoding.UTF8 });
// Workspace files
encodingOverride.push({ extension: WORKSPACE_EXTENSION, encoding: encoding.UTF8 });
// Folder Settings
this.contextService.getWorkspace().folders.forEach(folder => {
encodingOverride.push({ parent: uri.file(join(folder.uri.fsPath, '.vscode')), encoding: encoding.UTF8 });
});
return encodingOverride;
}
private getEncodingOverride(resource: uri): string {
if (resource && this.encodingOverride && this.encodingOverride.length) {
for (let i = 0; i < this.encodingOverride.length; i++) {
const override = this.encodingOverride[i];
// check if the resource is child of encoding override path
if (override.parent && isParent(resource.fsPath, override.parent.fsPath, !isLinux /* ignorecase */)) {
return override.encoding;
}
// check if the resource extension is equal to encoding override
if (override.extension && extname(resource.fsPath) === `.${override.extension}`) {
return override.encoding;
}
}
}
return null;
}
public dispose(): void {
this.toDispose = dispose(this.toDispose);
}
}
\ No newline at end of file
......@@ -44,8 +44,8 @@ import { IStorageService, StorageScope } from 'vs/platform/storage/common/storag
import { INotificationService, Severity } from 'vs/platform/notification/common/notification';
import { onUnexpectedError } from 'vs/base/common/errors';
import product from 'vs/platform/node/product';
import { WORKSPACE_EXTENSION } from 'vs/platform/workspaces/common/workspaces';
import { shell } from 'electron';
import { IEncodingOverride, ResourceEncodings } from 'vs/workbench/services/files/electron-browser/encoding';
class BufferPool {
......@@ -72,12 +72,6 @@ class BufferPool {
}
}
export interface IEncodingOverride {
parent?: uri;
extension?: string;
encoding: string;
}
export interface IFileServiceTestOptions {
disableWatcher?: boolean;
encodingOverride?: IEncodingOverride[];
......@@ -106,7 +100,7 @@ export class FileService implements IFileService {
private fileChangesWatchDelayer: ThrottledDelayer<void>;
private undeliveredRawFileChangesEvents: IRawFileChange[];
private encodingOverride: IEncodingOverride[];
private _encoding: ResourceEncodings;
constructor(
private contextService: IWorkspaceContextService,
......@@ -130,11 +124,15 @@ export class FileService implements IFileService {
this.fileChangesWatchDelayer = new ThrottledDelayer<void>(FileService.FS_EVENT_DELAY);
this.undeliveredRawFileChangesEvents = [];
this.encodingOverride = this.options.encodingOverride || this.getEncodingOverrides();
this._encoding = new ResourceEncodings(textResourceConfigurationService, environmentService, contextService, this.options.encodingOverride);
this.registerListeners();
}
public get encoding(): ResourceEncodings {
return this._encoding;
}
private registerListeners(): void {
// Wait until we are fully running before starting file watchers
......@@ -149,32 +147,10 @@ export class FileService implements IFileService {
}
}));
// Workspace Folder Change
this.toDispose.push(this.contextService.onDidChangeWorkspaceFolders(() => {
this.encodingOverride = this.getEncodingOverrides();
}));
// Lifecycle
this.lifecycleService.onShutdown(this.dispose, this);
}
private getEncodingOverrides(): IEncodingOverride[] {
const encodingOverride: IEncodingOverride[] = [];
// Global settings
encodingOverride.push({ parent: uri.file(this.environmentService.appSettingsHome), encoding: encoding.UTF8 });
// Workspace files
encodingOverride.push({ extension: WORKSPACE_EXTENSION, encoding: encoding.UTF8 });
// Folder Settings
this.contextService.getWorkspace().folders.forEach(folder => {
encodingOverride.push({ parent: uri.file(paths.join(folder.uri.fsPath, '.vscode')), encoding: encoding.UTF8 });
});
return encodingOverride;
}
private handleError(error: string | Error): void {
const msg = error ? error.toString() : void 0;
if (!msg) {
......@@ -548,7 +524,7 @@ export class FileService implements IFileService {
));
} else {
result.encoding = this.getEncoding(resource, this.getPeferredEncoding(resource, options, detected));
result.encoding = this._encoding.getReadEncoding(resource, options, detected);
result.stream = decoder = encoding.decodeStream(result.encoding);
resolve(result);
handleChunk(bytesRead);
......@@ -590,7 +566,7 @@ export class FileService implements IFileService {
// 2.) create parents as needed
return createParentsPromise.then(() => {
const encodingToWrite = this.getEncoding(resource, options.encoding);
const encodingToWrite = this._encoding.getWriteEncoding(resource, options.encoding);
let addBomPromise: TPromise<boolean> = TPromise.as(false);
// UTF_16 BE and LE as well as UTF_8 with BOM always have a BOM
......@@ -700,7 +676,7 @@ export class FileService implements IFileService {
return this.checkFileBeforeWriting(absolutePath, options, options.overwriteReadonly /* ignore readonly if we overwrite readonly, this is handled via sudo later */).then(exists => {
const writeOptions: IUpdateContentOptions = objects.assign(Object.create(null), options);
writeOptions.writeElevated = false;
writeOptions.encoding = this.getEncoding(resource, options.encoding);
writeOptions.encoding = this._encoding.getWriteEncoding(resource, options.encoding);
// 2.) write to a temporary file to be able to copy over later
const tmpPath = paths.join(os.tmpdir(), `code-elevated-${Math.random().toString(36).replace(/[^a-z]+/g, '').substr(0, 6)}`);
......@@ -999,69 +975,6 @@ export class FileService implements IFileService {
});
}
protected getPeferredEncoding(resource: uri, options: IResolveContentOptions, detected: encoding.IDetectedEncodingResult): string {
let preferredEncoding: string;
if (options && options.encoding) {
if (detected.encoding === encoding.UTF8 && options.encoding === encoding.UTF8) {
preferredEncoding = encoding.UTF8_with_bom; // indicate the file has BOM if we are to resolve with UTF 8
} else {
preferredEncoding = options.encoding; // give passed in encoding highest priority
}
} else if (detected.encoding) {
if (detected.encoding === encoding.UTF8) {
preferredEncoding = encoding.UTF8_with_bom; // if we detected UTF-8, it can only be because of a BOM
} else {
preferredEncoding = detected.encoding;
}
} else if (this.configuredEncoding(resource) === encoding.UTF8_with_bom) {
preferredEncoding = encoding.UTF8; // if we did not detect UTF 8 BOM before, this can only be UTF 8 then
}
return preferredEncoding;
}
public getEncoding(resource: uri, preferredEncoding?: string): string {
let fileEncoding: string;
const override = this.getEncodingOverride(resource);
if (override) {
fileEncoding = override;
} else if (preferredEncoding) {
fileEncoding = preferredEncoding;
} else {
fileEncoding = this.configuredEncoding(resource);
}
if (!fileEncoding || !encoding.encodingExists(fileEncoding)) {
fileEncoding = encoding.UTF8; // the default is UTF 8
}
return fileEncoding;
}
private configuredEncoding(resource: uri): string {
return this.textResourceConfigurationService.getValue(resource, 'files.encoding');
}
private getEncodingOverride(resource: uri): string {
if (resource && this.encodingOverride && this.encodingOverride.length) {
for (let i = 0; i < this.encodingOverride.length; i++) {
const override = this.encodingOverride[i];
// check if the resource is child of encoding override path
if (override.parent && isParent(resource.fsPath, override.parent.fsPath, !isLinux /* ignorecase */)) {
return override.encoding;
}
// check if the resource extension is equal to encoding override
if (override.extension && paths.extname(resource.fsPath) === `.${override.extension}`) {
return override.encoding;
}
}
}
return null;
}
public watchFileChanges(resource: uri): void {
assert.ok(resource && resource.scheme === Schemas.file, `Invalid resource for watching: ${resource}`);
......
......@@ -268,8 +268,7 @@ export class RemoteFileService extends FileService {
const decodeStreamOpts: IDecodeStreamOptions = {
guessEncoding: options.autoGuessEncoding,
overwriteEncoding: detected => {
const prefered = this.getPeferredEncoding(resource, options, { encoding: detected, seemsBinary: false });
return this.getEncoding(resource, prefered);
return this.encoding.getReadEncoding(resource, options, { encoding: detected, seemsBinary: false });
}
};
......@@ -332,7 +331,7 @@ export class RemoteFileService extends FileService {
}
private _writeFile(provider: IFileSystemProvider, resource: URI, content: string | ITextSnapshot, options: IUpdateContentOptions): TPromise<IFileStat> {
const encoding = this.getEncoding(resource, options.encoding);
const encoding = this.encoding.getWriteEncoding(resource, options.encoding);
// TODO@Joh support streaming API for remote file system writes
return provider.writeFile(resource, encode(typeof content === 'string' ? content : snapshotToString(content), encoding)).then(() => {
return this.resolveFile(resource);
......
......@@ -11,7 +11,7 @@ import * as os from 'os';
import * as assert from 'assert';
import { TPromise } from 'vs/base/common/winjs.base';
import { FileService, IEncodingOverride } from 'vs/workbench/services/files/electron-browser/fileService';
import { FileService } from 'vs/workbench/services/files/electron-browser/fileService';
import { FileOperation, FileOperationEvent, FileChangesEvent, FileOperationResult, FileOperationError } from 'vs/platform/files/common/files';
import uri from 'vs/base/common/uri';
import * as uuid from 'vs/base/common/uuid';
......@@ -22,6 +22,7 @@ import { TestEnvironmentService, TestContextService, TestTextResourceConfigurati
import { Workspace, toWorkspaceFolders } from 'vs/platform/workspace/common/workspace';
import { TestConfigurationService } from 'vs/platform/configuration/test/common/testConfigurationService';
import { TextModel } from 'vs/editor/common/model/textModel';
import { IEncodingOverride } from 'vs/workbench/services/files/electron-browser/encoding';
suite('FileService', () => {
let service: FileService;
......
......@@ -309,7 +309,7 @@ export class TextFileEditorModel extends BaseTextEditorModel implements ITextFil
mtime: Date.now(),
etag: void 0,
value: createTextBufferFactory(''), /* will be filled later from backup */
encoding: this.fileService.getEncoding(this.resource, this.preferredEncoding)
encoding: this.fileService.encoding.getWriteEncoding(this.resource, this.preferredEncoding)
};
return this.loadWithContent(content, backup);
......
......@@ -32,7 +32,7 @@ import { ServiceCollection } from 'vs/platform/instantiation/common/serviceColle
import { InstantiationService } from 'vs/platform/instantiation/common/instantiationService';
import { IEditorGroupService, GroupArrangement, GroupOrientation, IEditorTabOptions, IMoveOptions } from 'vs/workbench/services/group/common/groupService';
import { TextFileService } from 'vs/workbench/services/textfile/common/textFileService';
import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot } from 'vs/platform/files/common/files';
import { FileOperationEvent, IFileService, IResolveContentOptions, FileOperationError, IFileStat, IResolveFileResult, FileChangesEvent, IResolveFileOptions, IContent, IUpdateContentOptions, IStreamContent, ICreateFileOptions, ITextSnapshot, IResourceEncodings } from 'vs/platform/files/common/files';
import { IModelService } from 'vs/editor/common/services/modelService';
import { ModeServiceImpl } from 'vs/editor/common/services/modeServiceImpl';
import { ModelServiceImpl } from 'vs/editor/common/services/modelServiceImpl';
......@@ -680,6 +680,8 @@ export class TestFileService implements IFileService {
public _serviceBrand: any;
public encoding: IResourceEncodings;
private readonly _onFileChanges: Emitter<FileChangesEvent>;
private readonly _onAfterOperation: Emitter<FileOperationEvent>;
......@@ -811,7 +813,7 @@ export class TestFileService implements IFileService {
unwatchFileChanges(resource: URI): void {
}
getEncoding(resource: URI): string {
getWriteEncoding(resource: URI): string {
return 'utf8';
}
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册