提交 23458241 编写于 作者: A Alex Dima

Compute diff and dirtyDiff on a private editor worker

上级 63604de6
/*---------------------------------------------------------------------------------------------
* 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 {IWorker, IWorkerFactory} from './workerClient';
import {TPromise, ValueCallback, ErrorCallback} from 'vs/base/common/winjs.base';
import errors = require('vs/base/common/errors');
const INITIALIZE = '$initialize';
interface IMessage {
vsWorker: number;
req?: string;
seq?: string;
}
interface IRequestMessage extends IMessage {
req: string;
method: string;
args: any[];
}
interface IReplyMessage extends IMessage {
seq: string;
err: any;
res: any;
}
interface IMessageReply {
c: ValueCallback;
e: ErrorCallback;
}
interface IMessageHandler {
sendMessage(msg:string): void;
handleMessage(method:string, args:any[]): TPromise<any>;
}
class SimpleWorkerProtocol {
private _workerId: number;
private _lastSentReq: number;
private _pendingReplies: { [req:string]:IMessageReply; };
private _handler:IMessageHandler;
constructor(handler:IMessageHandler) {
this._workerId = -1;
this._handler = handler;
this._lastSentReq = 0;
this._pendingReplies = Object.create(null);
}
public setWorkerId(workerId:number): void {
this._workerId = workerId;
}
public sendMessage(method:string, args:any[]): TPromise<any> {
let req = String(++this._lastSentReq);
let reply: IMessageReply = {
c: null,
e: null
};
let result = new TPromise<any>((c, e, p) => {
reply.c = c;
reply.e = e;
}, () => {
// Cancel not supported
});
this._pendingReplies[req] = reply;
this._send({
vsWorker: this._workerId,
req: req,
method: method,
args: args
});
return result;
}
public handleMessage(serializedMessage:string): void {
let message:IMessage;
try {
message = JSON.parse(serializedMessage);
} catch(e) {
// nothing
}
if (!message.vsWorker) {
return;
}
if (this._workerId !== -1 && message.vsWorker !== this._workerId) {
return;
}
this._handleMessage(message);
}
private _handleMessage(msg:IMessage): void {
if (msg.seq) {
let replyMessage = <IReplyMessage>msg;
if (!this._pendingReplies[replyMessage.seq]) {
console.warn('Got reply to unknown seq');
return;
}
let reply = this._pendingReplies[replyMessage.seq];
delete this._pendingReplies[replyMessage.seq];
if (replyMessage.err) {
let err = replyMessage.err;
if (replyMessage.err.$isError) {
err = new Error();
err.name = replyMessage.err.name;
err.message = replyMessage.err.message;
err.stack = replyMessage.err.stack;
}
reply.e(err);
return;
}
reply.c(replyMessage.res);
return;
}
let requestMessage = <IRequestMessage>msg;
let req = requestMessage.req;
let result = this._handler.handleMessage(requestMessage.method, requestMessage.args);
result.then((r) => {
this._send({
vsWorker: this._workerId,
seq: req,
res: r,
err: undefined
});
}, (e) => {
this._send({
vsWorker: this._workerId,
seq: req,
res: undefined,
err: errors.transformErrorForSerialization(e)
});
});
}
private _send(msg:IRequestMessage|IReplyMessage): void {
let strMsg = JSON.stringify(msg);
// console.log('SENDING: ' + strMsg);
this._handler.sendMessage(strMsg);
}
}
/**
* Main thread side
*/
export class SimpleWorkerClient<T> {
private _worker:IWorker;
private _onModuleLoaded:TPromise<void>;
private _protocol: SimpleWorkerProtocol;
private _proxy: T;
constructor(workerFactory:IWorkerFactory, moduleId:string, ctor:any) {
this._worker = workerFactory.create('vs/base/common/worker/simpleWorker', (msg:string) => {
this._protocol.handleMessage(msg);
});
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg:string): void => {
this._worker.postMessage(msg);
},
handleMessage: (method:string, args:any[]): TPromise<any> => {
// Intentionally not supporting worker -> main requests
return TPromise.as(null);
}
});
this._protocol.setWorkerId(this._worker.getId());
// Gather loader configuration
let loaderConfiguration:any = null;
let globalRequire = (<any>window).require;
if (typeof globalRequire.getConfig === 'function') {
// Get the configuration from the Monaco AMD Loader
loaderConfiguration = globalRequire.getConfig();
} else if (typeof (<any>window).requirejs !== 'undefined') {
// Get the configuration from requirejs
loaderConfiguration = (<any>window).requirejs.s.contexts._.config;
}
// Send initialize message
this._onModuleLoaded = this._protocol.sendMessage(INITIALIZE, [
this._worker.getId(),
moduleId,
loaderConfiguration
]);
this._onModuleLoaded.then(null, () => this._onError('Worker failed to load ' + moduleId));
// Create proxy to loaded code
let proxyMethodRequest = (method:string, args:any[]):TPromise<any> => {
return this._request(method, args);
};
let createProxyMethod = (method:string, proxyMethodRequest:(method:string, args:any[])=>TPromise<any>): Function => {
return function () {
let args = Array.prototype.slice.call(arguments, 0);
return proxyMethodRequest(method, args);
};
};
this._proxy = <T><any>{};
for (let prop in ctor.prototype) {
if (ctor.prototype.hasOwnProperty(prop)) {
if (typeof ctor.prototype[prop] === 'function') {
this._proxy[prop] = createProxyMethod(prop, proxyMethodRequest);
}
}
}
}
public get(): T {
return this._proxy;
}
private _request(method:string, args:any[]): TPromise<any> {
return this._onModuleLoaded.then(() => {
return this._protocol.sendMessage(method, args);
});
}
private _onError(message:string, error?:any): void {
console.error(message);
console.info(error);
}
}
export interface IRequestHandler {
_requestHandlerTrait: any;
}
/**
* Worker side
*/
export class SimpleWorkerServer {
private _protocol: SimpleWorkerProtocol;
private _requestHandler: IRequestHandler;
constructor(postSerializedMessage:(msg:string)=>void) {
this._protocol = new SimpleWorkerProtocol({
sendMessage: (msg:string): void => {
postSerializedMessage(msg);
},
handleMessage: (method:string, args:any[]): TPromise<any> => this._handleMessage(method, args)
});
}
public onmessage(msg:string): void {
this._protocol.handleMessage(msg);
}
private _handleMessage(method: string, args:any[]): TPromise<any> {
if (method === INITIALIZE) {
return this.initialize(<number>args[0], <string>args[1], <any>args[2]);
}
if (!this._requestHandler || typeof this._requestHandler[method] !== 'function') {
return TPromise.wrapError(new Error('Missing requestHandler or method: ' + method));
}
try {
return TPromise.as(this._requestHandler[method].apply(this._requestHandler, args));
} catch (e) {
return TPromise.wrapError(e);
}
}
private initialize(workerId: number, moduleId: string, loaderConfig:any): TPromise<any> {
this._protocol.setWorkerId(workerId);
// TODO@Alex: share this code with workerServer
if (loaderConfig) {
// Remove 'baseUrl', handling it is beyond scope for now
if (typeof loaderConfig.baseUrl !== 'undefined') {
delete loaderConfig['baseUrl'];
}
if (typeof loaderConfig.paths !== 'undefined') {
if (typeof loaderConfig.paths.vs !== 'undefined') {
delete loaderConfig.paths['vs'];
}
}
let nlsConfig = loaderConfig['vs/nls'];
// We need to have pseudo translation
if (nlsConfig && nlsConfig.pseudo) {
require(['vs/nls'], function(nlsPlugin) {
nlsPlugin.setPseudoTranslation(nlsConfig.pseudo);
});
}
// Since this is in a web worker, enable catching errors
loaderConfig.catchError = true;
(<any>self).require.config(loaderConfig);
}
let cc: ValueCallback;
let ee: ErrorCallback;
let r = new TPromise<any>((c, e, p) => {
cc = c;
ee = e;
});
require([moduleId], (...result:any[]) => {
let handlerModule = result[0];
this._requestHandler = handlerModule.create();
cc(null);
}, ee);
return r;
}
}
/**
* Called on the worker side
*/
export function create(postMessage:(msg:string)=>void): SimpleWorkerServer {
return new SimpleWorkerServer(postMessage);
}
......@@ -22,7 +22,7 @@ export interface IWorkerCallback {
}
export interface IWorkerFactory {
create(id:number, callback:IWorkerCallback, onCrashCallback?:()=>void):IWorker;
create(moduleId:string, callback:IWorkerCallback, onCrashCallback?:()=>void):IWorker;
}
interface IActiveRequest {
......@@ -35,11 +35,8 @@ interface IActiveRequest {
export class WorkerClient {
private static LAST_WORKER_ID = 0;
private _lastMessageId:number;
private _promises:{[id:string]:IActiveRequest;};
private _workerId:number;
private _worker:IWorker;
private _messagesQueue:protocol.IClientMessage[];
......@@ -52,7 +49,7 @@ export class WorkerClient {
public onModuleLoaded:TPromise<void>;
constructor(workerFactory:IWorkerFactory, moduleId:string, decodeMessageName:(msg:protocol.IClientMessage)=>string, onCrashCallback:(workerClient:WorkerClient)=>void, workerId:number=++WorkerClient.LAST_WORKER_ID) {
constructor(workerFactory:IWorkerFactory, moduleId:string, decodeMessageName:(msg:protocol.IClientMessage)=>string) {
this._decodeMessageName = decodeMessageName;
this._lastMessageId = 0;
this._promises = {};
......@@ -61,11 +58,8 @@ export class WorkerClient {
this._processQueueTimeout = -1;
this._waitingForWorkerReply = false;
this._lastTimerEvent = null;
this._workerId = workerId;
this._worker = workerFactory.create(workerId, (msg) => this._onSerializedMessage(msg), () => {
onCrashCallback(this);
});
this._worker = workerFactory.create('vs/base/common/worker/workerServer', (msg) => this._onSerializedMessage(msg));
let loaderConfiguration:any = null;
......@@ -95,10 +89,6 @@ export class WorkerClient {
return this._remoteCom;
}
public get workerId():number {
return this._workerId;
}
public getQueueSize(): number {
return this._messagesQueue.length + (this._waitingForWorkerReply ? 1 : 0);
}
......
......@@ -160,6 +160,7 @@ export class WorkerServer {
this._workerId = msg.payload.id;
var loaderConfig = msg.payload.loaderConfiguration;
// TODO@Alex: share this code with simpleWorker
if (loaderConfig) {
// Remove 'baseUrl', handling it is beyond scope for now
if (typeof loaderConfig.baseUrl !== 'undefined') {
......
......@@ -24,9 +24,10 @@ class WebWorker implements IWorker {
private id:number;
private worker:any;
constructor(id:number, label:string, onMessageCallback:IWorkerCallback) {
constructor(moduleId:string, id:number, label:string, onMessageCallback:IWorkerCallback) {
this.id = id;
this.worker = new Worker(getWorkerUrl('workerMain.js', label));
this.postMessage(moduleId);
this.worker.onmessage = function (ev:any) {
onMessageCallback(ev.data);
};
......@@ -60,7 +61,7 @@ class FrameWorker implements IWorker {
private _listeners: lifecycle.IDisposable[];
constructor(id: number, onMessageCallback:IWorkerCallback) {
constructor(moduleId:string, id: number, onMessageCallback:IWorkerCallback) {
this.id = id;
this._listeners = [];
......@@ -68,6 +69,8 @@ class FrameWorker implements IWorker {
this.loaded = false;
this.beforeLoadMessages = [];
this.postMessage(moduleId);
this.iframe = <HTMLIFrameElement> document.createElement('iframe');
this.iframe.id = this.iframeId();
this.iframe.src = require.toUrl('./workerMainCompatibility.html');
......@@ -121,13 +124,14 @@ class FrameWorker implements IWorker {
}
export class DefaultWorkerFactory implements IWorkerFactory {
public create(id:number, onMessageCallback:IWorkerCallback, onCrashCallback:()=>void = null):IWorker {
var result:IWorker = null;
try {
result = new WebWorker(id, 'service' + id, onMessageCallback);
} catch (e) {
result = new FrameWorker(id, onMessageCallback);
private static LAST_WORKER_ID = 0;
public create(moduleId:string, onMessageCallback:IWorkerCallback):IWorker {
var workerId = (++DefaultWorkerFactory.LAST_WORKER_ID);
if (typeof WebWorker !== 'undefined') {
return new WebWorker(moduleId, workerId, 'service' + workerId, onMessageCallback);
}
return result;
return new FrameWorker(moduleId, workerId, onMessageCallback);
}
}
\ No newline at end of file
......@@ -16,20 +16,28 @@
catchError: true
});
var beforeReadyMessages:any[] = [];
self.onmessage = (message) => beforeReadyMessages.push(message);
// Note: not using a import-module statement here, because
// it would wrap above statements in the define call.
require(['vs/base/common/worker/workerServer'], function(ws) {
var messageHandler = ws.create((msg:any) => {
(<any>self).postMessage(msg);
}, null);
self.onmessage = (e) => messageHandler.onmessage(e.data);
while(beforeReadyMessages.length > 0) {
self.onmessage(beforeReadyMessages.shift());
var loadCode = function(moduleId) {
require([moduleId], function(ws) {
var messageHandler = ws.create((msg:any) => {
(<any>self).postMessage(msg);
}, null);
self.onmessage = (e) => messageHandler.onmessage(e.data);
while(beforeReadyMessages.length > 0) {
self.onmessage(beforeReadyMessages.shift());
}
});
};
var isFirstMessage = true;
var beforeReadyMessages:MessageEvent[] = [];
self.onmessage = (message) => {
if (!isFirstMessage) {
beforeReadyMessages.push(message);
return;
}
});
isFirstMessage = false;
loadCode(message.data);
};
})();
......@@ -4,17 +4,6 @@
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<script type="text/javascript">
var beforeReadyMessages = [];
var beforeReadyListener = function (message) {
beforeReadyMessages.push(message.data);
};
if (window.attachEvent) {
window.attachEvent('onmessage', beforeReadyListener);
} else {
window.onmessage = beforeReadyListener;
}
</script>
<script type="text/javascript" src="../../loader.js"></script>
<script>
require.config({
......@@ -22,25 +11,47 @@
catchError: true
});
require(['vs/base/common/worker/workerServer'], function(ws) {
var workerServer = ws.create(function (msg) {
window.parent.postMessage(msg, '*');
});
window.isEmulatedWorker = true;
(function() {
var isFirstMessage = true;
var beforeReadyMessages = [];
var loadCode = function(moduleId) {
require([moduleId], function(ws) {
var workerServer = ws.create(function (msg) {
window.parent.postMessage(msg, '*');
});
var readyListener = function (ev) {
workerServer.onmessage(ev.data);
};
if (window.attachEvent) {
window.detachEvent('onmessage', beforeReadyListener);
window.attachEvent('onmessage', readyListener);
} else {
window.onmessage = readyListener;
}
while(beforeReadyMessages.length > 0) {
readyListener({ data: beforeReadyMessages.shift() });
}
});
};
var beforeReadyListener = function (message) {
if (!isFirstMessage) {
beforeReadyMessages.push(message.data);
return;
}
var readyListener = function (ev) {
workerServer.onmessage(ev.data);
isFirstMessage = false;
loadCode(message.data);
};
if (window.attachEvent) {
window.detachEvent('onmessage', beforeReadyListener);
window.attachEvent('onmessage', readyListener);
window.attachEvent('onmessage', beforeReadyListener);
} else {
window.onmessage = readyListener;
}
while(beforeReadyMessages.length > 0) {
readyListener({ data: beforeReadyMessages.shift() });
window.onmessage = beforeReadyListener;
}
});
})();
</script>
</head>
<body>
......
......@@ -37,6 +37,7 @@ import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {IJSONSchema} from 'vs/base/common/jsonSchema';
import * as JSONContributionRegistry from 'vs/platform/jsonschemas/common/jsonContributionRegistry';
import {ILanguageExtensionPoint} from 'vs/editor/common/services/modeService';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
// Set defaults for standalone editor
DefaultConfig.editor.wrappingIndent = 'none';
......@@ -171,7 +172,18 @@ class StandaloneDiffEditor extends DiffEditorWidget.DiffEditorWidget {
private _markerService: IMarkerService;
private _telemetryService: ITelemetryService;
constructor(domElement:HTMLElement, options:IDiffEditorConstructionOptions, toDispose: Lifecycle.IDisposable[], @IInstantiationService instantiationService: IInstantiationService, @IKeybindingService keybindingService: IKeybindingService, @IContextViewService contextViewService: IContextViewService, @IEditorService editorService: IEditorService, @IMarkerService markerService: IMarkerService, @ITelemetryService telemetryService: ITelemetryService) {
constructor(
domElement:HTMLElement,
options:IDiffEditorConstructionOptions,
toDispose: Lifecycle.IDisposable[],
@IInstantiationService instantiationService: IInstantiationService,
@IKeybindingService keybindingService: IKeybindingService,
@IContextViewService contextViewService: IContextViewService,
@IEditorService editorService: IEditorService,
@IMarkerService markerService: IMarkerService,
@ITelemetryService telemetryService: ITelemetryService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService
) {
if (keybindingService instanceof AbstractKeybindingService) {
(<AbstractKeybindingService><any>keybindingService).setInstantiationService(instantiationService);
}
......@@ -189,7 +201,7 @@ class StandaloneDiffEditor extends DiffEditorWidget.DiffEditorWidget {
options = options || {};
super(domElement, options, instantiationService);
super(domElement, options, editorWorkerService, instantiationService);
this._contextViewService.setContainer(this._containerDomElement);
}
......
......@@ -19,6 +19,8 @@ import {BaseWorkspaceContextService} from 'vs/platform/workspace/common/baseWork
import _eventService = require('vs/platform/event/common/eventService');
import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServiceImpl';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {EditorWorkerServiceImpl} from 'vs/editor/common/services/editorWorkerServiceImpl';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
import {IModelService} from 'vs/editor/common/services/modelService';
import {IStorageService} from 'vs/platform/storage/common/storage';
import {IConfigurationService} from 'vs/platform/configuration/common/configuration';
......@@ -67,6 +69,7 @@ export interface IEditorOverrideServices {
fileService?:IFileService;
modelService?: IModelService;
codeEditorService?: ICodeEditorService;
editorWorkerService?: IEditorWorkerService;
}
export interface IStaticServices {
......@@ -80,6 +83,7 @@ export interface IStaticServices {
telemetryService: ITelemetryService;
modelService: IModelService;
codeEditorService: ICodeEditorService;
editorWorkerService: IEditorWorkerService;
eventService: IEventService;
instantiationService: IInstantiationService;
}
......@@ -110,6 +114,7 @@ export function ensureStaticPlatformServices(services: IEditorOverrideServices):
services.messageService = services.messageService || statics.messageService;
services.modelService = services.modelService || statics.modelService;
services.codeEditorService = services.codeEditorService || statics.codeEditorService;
services.editorWorkerService = services.editorWorkerService || statics.editorWorkerService;
services.eventService = services.eventService || statics.eventService;
services.markerService = services.markerService || statics.markerService;
services.instantiationService = statics.instantiationService;
......@@ -192,6 +197,7 @@ export function getOrCreateStaticServices(services?: IEditorOverrideServices): I
}
var codeEditorService = services.codeEditorService || new CodeEditorServiceImpl();
var editorWorkerService = services.editorWorkerService || new EditorWorkerServiceImpl(modelService);
var eventService = services.eventService || new _eventService.EventService();
......@@ -206,6 +212,7 @@ export function getOrCreateStaticServices(services?: IEditorOverrideServices): I
messageService: messageService,
modelService: modelService,
codeEditorService: codeEditorService,
editorWorkerService: editorWorkerService,
eventService: eventService,
instantiationService: void 0
};
......
......@@ -22,6 +22,7 @@ import {Range} from 'vs/editor/common/core/range';
import {IInstantiationService} from 'vs/platform/instantiation/common/instantiation';
import {renderLine} from 'vs/editor/common/viewLayout/viewLineRenderer';
import {StyleMutator} from 'vs/base/browser/styleMutator';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
interface IEditorScrollEvent {
scrollLeft: number;
......@@ -170,8 +171,16 @@ export class DiffEditorWidget extends EventEmitter.EventEmitter implements Edito
private _updateDecorationsRunner:Schedulers.RunOnceScheduler;
constructor(domElement:HTMLElement, options:EditorCommon.IDiffEditorOptions, @IInstantiationService instantiationService: IInstantiationService) {
private _editorWorkerService: IEditorWorkerService;
constructor(
domElement:HTMLElement,
options:EditorCommon.IDiffEditorOptions,
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
@IInstantiationService instantiationService: IInstantiationService
) {
super();
this._editorWorkerService = editorWorkerService;
this.id = (++DIFF_EDITOR_ID);
......@@ -716,39 +725,26 @@ export class DiffEditorWidget extends EventEmitter.EventEmitter implements Edito
var currentOriginalModel = this.originalEditor.getModel();
var currentModifiedModel = this.modifiedEditor.getModel();
var diffSupport = this.modifiedEditor.getModel().getMode().diffSupport;
if(!diffSupport) {
// no diffing support
this._lineChanges = null;
this._updateDecorationsRunner.schedule();
} else {
try {
diffSupport.computeDiff(currentOriginalModel.getAssociatedResource(), currentModifiedModel.getAssociatedResource(), this._ignoreTrimWhitespace).then((result:EditorCommon.ILineChange[]) => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this.originalEditor.getModel()
&& currentModifiedModel === this.modifiedEditor.getModel()
)
{
this._lineChanges = result;
this._updateDecorationsRunner.schedule();
this.emit(EditorCommon.EventType.DiffUpdated, { editor: this, lineChanges: result });
}
}, (error) => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this.originalEditor.getModel()
&& currentModifiedModel === this.modifiedEditor.getModel()
)
{
this._lineChanges = null;
this._updateDecorationsRunner.schedule();
}
});
} catch(e) {
console.error(e);
this._editorWorkerService.computeDiff(currentOriginalModel.getAssociatedResource(), currentModifiedModel.getAssociatedResource(), this._ignoreTrimWhitespace).then((result) => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this.originalEditor.getModel()
&& currentModifiedModel === this.modifiedEditor.getModel()
)
{
this._lineChanges = result;
this._updateDecorationsRunner.schedule();
this.emit(EditorCommon.EventType.DiffUpdated, { editor: this, lineChanges: result });
}
}, (error) => {
if (currentToken === this._diffComputationToken
&& currentOriginalModel === this.originalEditor.getModel()
&& currentModifiedModel === this.modifiedEditor.getModel()
)
{
this._lineChanges = null;
this._updateDecorationsRunner.schedule();
}
}
});
}
private _cleanViewZonesAndDecorations(): void {
......
/*---------------------------------------------------------------------------------------------
* 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 EditorCommon = require('vs/editor/common/editorCommon');
import {TPromise} from 'vs/base/common/winjs.base';
import {IRequestHandler} from 'vs/base/common/worker/simpleWorker';
import {EditorSimpleWorker, IRawModelData} from 'vs/editor/common/services/editorSimpleWorkerCommon';
import {MirrorModel2} from 'vs/editor/common/model/mirrorModel2';
import URI from 'vs/base/common/uri';
import {DiffComputer} from 'vs/editor/common/diff/diffComputer';
class MirrorModel extends MirrorModel2 {
public getLinesContent(): string[] {
return this._lines.slice(0);
}
}
export class EditorSimpleWorkerImpl extends EditorSimpleWorker implements IRequestHandler {
_requestHandlerTrait: any;
private _models:{[uri:string]:MirrorModel;};
constructor() {
super();
this._models = Object.create(null);
}
public acceptNewModel(data:IRawModelData): void {
this._models[data.url] = new MirrorModel(URI.parse(data.url), data.value.lines, data.value.EOL, data.versionId);
}
public acceptModelChanged(strURL: string, events: EditorCommon.IModelContentChangedEvent2[]): void {
if (!this._models[strURL]) {
return;
}
let model = this._models[strURL];
model.onEvents(events);
}
public acceptRemovedModel(strURL: string): void {
if (!this._models[strURL]) {
return;
}
delete this._models[strURL];
}
// ---- BEGIN diff --------------------------------------------------------------------------
public computeDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean): TPromise<EditorCommon.ILineChange[]> {
let original = this._models[originalUrl];
let modified = this._models[modifiedUrl];
if (!original || !modified) {
return null;
}
let originalLines = original.getLinesContent();
let modifiedLines = modified.getLinesContent();
let diffComputer = new DiffComputer(originalLines, modifiedLines, {
shouldPostProcessCharChanges: true,
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
shouldConsiderTrimWhitespaceInEmptyCase: true
});
return TPromise.as(diffComputer.computeDiff());
}
public computeDirtyDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.IChange[]> {
let original = this._models[originalUrl];
let modified = this._models[modifiedUrl];
if (!original || !modified) {
return null;
}
let originalLines = original.getLinesContent();
let modifiedLines = modified.getLinesContent();
let diffComputer = new DiffComputer(originalLines, modifiedLines, {
shouldPostProcessCharChanges: false,
shouldIgnoreTrimWhitespace: ignoreTrimWhitespace,
shouldConsiderTrimWhitespaceInEmptyCase: false
});
return TPromise.as(diffComputer.computeDiff());
}
// ---- END diff --------------------------------------------------------------------------
}
/**
* Called on the worker side
*/
export function create(): IRequestHandler {
return new EditorSimpleWorkerImpl();
}
/*---------------------------------------------------------------------------------------------
* 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 {TPromise} from 'vs/base/common/winjs.base';
import EditorCommon = require('vs/editor/common/editorCommon');
export interface IRawModelData {
url:string;
versionId:number;
value:EditorCommon.IRawText;
}
export abstract class EditorSimpleWorker {
public acceptNewModel(data:IRawModelData): void {
throw new Error('Not implemented!');
}
public acceptModelChanged(strURL: string, events: EditorCommon.IModelContentChangedEvent2[]): void {
throw new Error('Not implemented!');
}
public acceptRemovedModel(strURL: string): void {
throw new Error('Not implemented!');
}
public computeDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.ILineChange[]> {
throw new Error('Not implemented!');
}
public computeDirtyDiff(originalUrl:string, modifiedUrl:string, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.IChange[]> {
throw new Error('Not implemented!');
}
}
/*---------------------------------------------------------------------------------------------
* 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 {createDecorator, ServiceIdentifier} from 'vs/platform/instantiation/common/instantiation';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import EditorCommon = require('vs/editor/common/editorCommon');
export var ID_EDITOR_WORKER_SERVICE = 'workerService';
export var IEditorWorkerService = createDecorator<IEditorWorkerService>(ID_EDITOR_WORKER_SERVICE);
export interface IEditorWorkerService {
serviceId: ServiceIdentifier<any>;
computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.ILineChange[]>;
computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.IChange[]>;
}
/*---------------------------------------------------------------------------------------------
* 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 {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
import URI from 'vs/base/common/uri';
import {TPromise} from 'vs/base/common/winjs.base';
import EditorCommon = require('vs/editor/common/editorCommon');
import {IModelService} from 'vs/editor/common/services/modelService';
import {IDisposable, disposeAll} from 'vs/base/common/lifecycle';
import {SimpleWorkerClient} from 'vs/base/common/worker/simpleWorker';
import {DefaultWorkerFactory} from 'vs/base/worker/defaultWorkerFactory';
import {EditorSimpleWorker} from 'vs/editor/common/services/editorSimpleWorkerCommon';
export class EditorWorkerServiceImpl implements IEditorWorkerService {
public serviceId = IEditorWorkerService;
private _workerManager:WorkerManager;
constructor(modelService:IModelService) {
this._workerManager = new WorkerManager(modelService);
}
computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.ILineChange[]> {
return this._workerManager.withWorker().then(client => client.computeDiff(original, modified, ignoreTrimWhitespace));
}
computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.IChange[]> {
return this._workerManager.withWorker().then(client => client.computeDirtyDiff(original, modified, ignoreTrimWhitespace));
}
}
class WorkerManager {
private _modelService:IModelService;
private _editorWorkerClient: EditorWorkerClient;
constructor(modelService:IModelService) {
this._modelService = modelService;
this._editorWorkerClient = null;
}
public withWorker(): TPromise<EditorWorkerClient> {
if (!this._editorWorkerClient) {
this._editorWorkerClient = new EditorWorkerClient(this._modelService);
}
return TPromise.as(this._editorWorkerClient);
}
}
class EditorWorkerClient {
private _worker: SimpleWorkerClient<EditorSimpleWorker>;
private _proxy: EditorSimpleWorker;
private _modelService:IModelService;
private _syncedModels: {[uri:string]:IDisposable[];};
constructor(modelService:IModelService) {
this._modelService = modelService;
this._syncedModels = Object.create(null);
this._worker = new SimpleWorkerClient<EditorSimpleWorker>(
new DefaultWorkerFactory(),
'vs/editor/common/services/editorSimpleWorker',
EditorSimpleWorker
);
this._proxy = this._worker.get();
}
public dispose(): void {
console.log('TODO: EditorWorkerClient.dispose');
}
public computeDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.ILineChange[]> {
return this._withSyncedResources([original, modified]).then(_ => {
return this._proxy.computeDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
});
}
public computeDirtyDiff(original:URI, modified:URI, ignoreTrimWhitespace:boolean):TPromise<EditorCommon.IChange[]> {
return this._withSyncedResources([original, modified]).then(_ => {
return this._proxy.computeDirtyDiff(original.toString(), modified.toString(), ignoreTrimWhitespace);
});
}
private _withSyncedResources(resources:URI[]): TPromise<void> {
for (let i = 0; i < resources.length; i++) {
let resource = resources[i];
let resourceStr = resource.toString();
if (!this._syncedModels[resourceStr]) {
this._beginModelSync(resource);
}
}
return TPromise.as(null);
}
private _beginModelSync(resource:URI): void {
let modelUrl = resource.toString();
let model = this._modelService.getModel(resource);
if (!model) {
throw new Error('Uknown model!');
}
if (model.isTooLargeForHavingARichMode()) {
return;
}
this._proxy.acceptNewModel({
url: model.getAssociatedResource().toString(),
value: model.toRawText(),
versionId: model.getVersionId()
});
let toDispose:IDisposable[] = [];
toDispose.push(model.addBulkListener2((events) => {
let changedEvents: EditorCommon.IModelContentChangedEvent2[] = [];
for (let i = 0, len = events.length; i < len; i++) {
let e = events[i];
switch (e.getType()) {
case EditorCommon.EventType.ModelContentChanged2:
changedEvents.push(<EditorCommon.IModelContentChangedEvent2>e.getData());
break;
case EditorCommon.EventType.ModelDispose:
this._stopModelSync(modelUrl);
return;
}
}
if (changedEvents.length > 0) {
this._proxy.acceptModelChanged(modelUrl.toString(), changedEvents);
}
}));
toDispose.push({
dispose: () => {
this._proxy.acceptRemovedModel(modelUrl);
}
});
this._syncedModels[modelUrl] = toDispose;
}
private _stopModelSync(modelUrl:string): void {
let toDispose = this._syncedModels[modelUrl];
delete this._syncedModels[modelUrl];
disposeAll(toDispose);
}
}
......@@ -134,7 +134,7 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi
return major.substring(major.length - 14) + '.' + minor.substr(0, 14);
}
private _doCreateWorker(workerId?: number): Worker.WorkerClient {
private _doCreateWorker(): Worker.WorkerClient {
let worker = new Worker.WorkerClient(
this._workerFactory,
this._workerModuleId,
......@@ -143,22 +143,7 @@ export class MainThreadService extends abstractThreadService.AbstractThreadServi
return this._shortName(msg.payload[0], msg.payload[1]);
}
return msg.type;
},
(crashed: Worker.WorkerClient) => {
let index = 0;
for (; index < this._workerPool.length; index++) {
if (crashed === this._workerPool[index]) {
break;
}
}
let newWorker = this._doCreateWorker(crashed.workerId);
if (crashed === this._workerPool[index]) {
this._workerPool[index] = newWorker;
} else {
this._workerPool.push(newWorker);
}
},
workerId
}
);
worker.getRemoteCom().setManyHandler(this);
worker.onModuleLoaded = worker.request('initialize', {
......
......@@ -50,6 +50,8 @@ import {IModelService} from 'vs/editor/common/services/modelService';
import {ModelServiceImpl} from 'vs/editor/common/services/modelServiceImpl';
import {CodeEditorServiceImpl} from 'vs/editor/browser/services/codeEditorServiceImpl';
import {ICodeEditorService} from 'vs/editor/common/services/codeEditorService';
import {EditorWorkerServiceImpl} from 'vs/editor/common/services/editorWorkerServiceImpl';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
import {MainProcessVSCodeAPIHelper} from 'vs/workbench/api/node/extHost.api.impl';
import {MainProcessPluginService} from 'vs/platform/plugins/common/nativePluginService';
import {MainThreadDocuments} from 'vs/workbench/api/node/extHostDocuments';
......@@ -300,6 +302,7 @@ export class WorkbenchShell {
result.addSingleton(IMarkerService, markerService);
result.addSingleton(IModelService, modelService);
result.addSingleton(ICodeEditorService, new CodeEditorServiceImpl());
result.addSingleton(IEditorWorkerService, new EditorWorkerServiceImpl(modelService));
result.addSingleton(IThemeService, this.themeService);
result.addSingleton(IActionsService, new ActionsService(pluginService, this.keybindingService));
......
......@@ -37,6 +37,10 @@ import {IWorkspaceContextService} from 'vs/platform/workspace/common/workspace';
import {IViewletService} from 'vs/workbench/services/viewlet/common/viewletService';
import {IWorkbenchEditorService} from 'vs/workbench/services/editor/common/editorService';
import {KeyMod, KeyCode} from 'vs/base/common/keyCodes';
import {IModelService} from 'vs/editor/common/services/modelService';
import {TextModel} from 'vs/editor/common/model/textModel';
import {IEditorWorkerService} from 'vs/editor/common/services/editorWorkerService';
import URI from 'vs/base/common/uri';
import IGitService = git.IGitService;
......@@ -102,6 +106,7 @@ export class StatusUpdater implements ext.IWorkbenchContribution
}
class DirtyDiffModelDecorator {
static GIT_ORIGINAL_SCHEME = 'git-index';
static ID = 'Monaco.IDE.UI.Viewlets.GitViewlet.Editor.DirtyDiffDecorator';
static MODIFIED_DECORATION_OPTIONS: common.IModelDecorationOptions = {
......@@ -132,32 +137,38 @@ class DirtyDiffModelDecorator {
}
};
private modelService: IModelService;
private editorWorkerService: IEditorWorkerService;
private editorService: IWorkbenchEditorService;
private contextService: IWorkspaceContextService;
private gitService: IGitService;
private model: common.IModel;
private _originalContentsURI: URI;
private path: string;
private decorations: string[];
private firstRun: boolean;
private delayer: async.ThrottledDelayer<void>;
private diffDelayer: async.ThrottledDelayer<void>;
private toDispose: lifecycle.IDisposable[];
constructor(model: common.IModel, path: string,
@IModelService modelService: IModelService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService,
@IWorkbenchEditorService editorService: IWorkbenchEditorService,
@IWorkspaceContextService contextService: IWorkspaceContextService,
@IGitService gitService: IGitService
) {
this.modelService = modelService;
this.editorWorkerService = editorWorkerService;
this.editorService = editorService;
this.contextService = contextService;
this.gitService = gitService;
this.model = model;
this._originalContentsURI = model.getAssociatedResource().withScheme(DirtyDiffModelDecorator.GIT_ORIGINAL_SCHEME);
this.path = path;
this.decorations = [];
this.firstRun = true;
this.delayer = new async.ThrottledDelayer<void>(500);
this.diffDelayer = new async.ThrottledDelayer<void>(200);
......@@ -193,6 +204,26 @@ class DirtyDiffModelDecorator {
.done(null, errors.onUnexpectedError);
}
private static _stringArrEquals(a:string[], b:string[]): boolean {
if (a.length !== b.length) {
return false;
}
for (let i = 0, len = a.length; i < len; i++) {
if (a[i] !== b[i]) {
return false;
}
}
return true;
}
private static _equals(model:common.IModel, rawText:common.IRawText): boolean {
if (!model) {
return false;
}
let modelRawText = model.toRawText();
return this._stringArrEquals(modelRawText.lines, rawText.lines);
}
private diffOriginalContents(): winjs.TPromise<void> {
return this.getOriginalContents()
.then(contents => {
......@@ -200,16 +231,23 @@ class DirtyDiffModelDecorator {
return; // disposed
}
let rawText = TextModel.toRawText(contents);
let originalModel = this.modelService.getModel(this._originalContentsURI);
// return early if nothing has changed
if (!this.firstRun && this.model.getProperty('original') === contents) {
if (DirtyDiffModelDecorator._equals(originalModel, rawText)) {
return winjs.TPromise.as(null);
}
this.firstRun = false;
this.model.setProperty('original', contents);
if (!originalModel) {
// this is the first time we load the original contents
this.modelService.createModel(contents, null, this._originalContentsURI);
} else {
// we already have the original contents
originalModel.setValue(contents);
}
// wait a bit, for the 'original' property to propagate
return winjs.TPromise.timeout(500).then(() => this.triggerDiff());
return this.triggerDiff();
});
}
......@@ -230,13 +268,7 @@ class DirtyDiffModelDecorator {
return winjs.TPromise.as<any>([]); // disposed
}
var mode = this.model.getMode(); // might be null
if (!mode || !mode.dirtyDiffSupport) {
return winjs.TPromise.as<any>([]);
}
return mode.dirtyDiffSupport.computeDirtyDiff(this.model.getAssociatedResource(), true);
return this.editorWorkerService.computeDirtyDiff(this._originalContentsURI, this.model.getAssociatedResource(), true);
}).then((diff:common.IChange[]) => {
if (!this.model || this.model.isDisposed()) {
return; // disposed
......@@ -285,6 +317,7 @@ class DirtyDiffModelDecorator {
}
public dispose(): void {
this.modelService.destroyModel(this._originalContentsURI);
this.toDispose = lifecycle.disposeAll(this.toDispose);
if (this.model && !this.model.isDisposed()) {
this.model.deltaDecorations(this.decorations, []);
......
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册