mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-25 11:08:51 +01:00
debt - no guessing-caching, better use of gc-signals, command converter using gc-signals, main side heap service
This commit is contained in:
@@ -28,8 +28,7 @@ import { ExtHostMessageService } from 'vs/workbench/api/node/extHostMessageServi
|
||||
import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors';
|
||||
import { ExtHostLanguages } from 'vs/workbench/api/node/extHostLanguages';
|
||||
import { ExtHostLanguageFeatures } from 'vs/workbench/api/node/extHostLanguageFeatures';
|
||||
import * as ExtHostTypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { registerApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
import { ExtHostApiCommands } from 'vs/workbench/api/node/extHostApiCommands';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import Modes = require('vs/editor/common/modes');
|
||||
import URI from 'vs/base/common/uri';
|
||||
@@ -58,16 +57,17 @@ export interface IExtensionApiFactory {
|
||||
export function createApiFactory(threadService: IThreadService, extensionService: ExtHostExtensionService, contextService: IWorkspaceContextService, telemetryService: ITelemetryService): IExtensionApiFactory {
|
||||
|
||||
|
||||
|
||||
// Addressable instances
|
||||
const col = new InstanceCollection();
|
||||
const extHostHeapMonitor = col.define(ExtHostContext.ExtHostHeapService).set<ExtHostHeapService>(new ExtHostHeapService());
|
||||
const extHostHeapService = col.define(ExtHostContext.ExtHostHeapService).set<ExtHostHeapService>(new ExtHostHeapService());
|
||||
const extHostDocuments = col.define(ExtHostContext.ExtHostDocuments).set<ExtHostDocuments>(new ExtHostDocuments(threadService));
|
||||
const extHostDocumentSaveParticipant = col.define(ExtHostContext.ExtHostDocumentSaveParticipant).set<ExtHostDocumentSaveParticipant>(new ExtHostDocumentSaveParticipant(extHostDocuments, threadService.get(MainContext.MainThreadWorkspace)));
|
||||
const extHostEditors = col.define(ExtHostContext.ExtHostEditors).set<ExtHostEditors>(new ExtHostEditors(threadService, extHostDocuments));
|
||||
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostEditors));
|
||||
const extHostCommands = col.define(ExtHostContext.ExtHostCommands).set<ExtHostCommands>(new ExtHostCommands(threadService, extHostEditors, extHostHeapService));
|
||||
const extHostConfiguration = col.define(ExtHostContext.ExtHostConfiguration).set<ExtHostConfiguration>(new ExtHostConfiguration(threadService.get(MainContext.MainThreadConfiguration)));
|
||||
const extHostDiagnostics = col.define(ExtHostContext.ExtHostDiagnostics).set<ExtHostDiagnostics>(new ExtHostDiagnostics(threadService));
|
||||
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapMonitor, extHostDiagnostics));
|
||||
const languageFeatures = col.define(ExtHostContext.ExtHostLanguageFeatures).set<ExtHostLanguageFeatures>(new ExtHostLanguageFeatures(threadService, extHostDocuments, extHostCommands, extHostHeapService, extHostDiagnostics));
|
||||
const extHostFileSystemEvent = col.define(ExtHostContext.ExtHostFileSystemEventService).set<ExtHostFileSystemEventService>(new ExtHostFileSystemEventService());
|
||||
const extHostQuickOpen = col.define(ExtHostContext.ExtHostQuickOpen).set<ExtHostQuickOpen>(new ExtHostQuickOpen(threadService));
|
||||
const extHostTerminalService = col.define(ExtHostContext.ExtHostTerminalService).set<ExtHostTerminalService>(new ExtHostTerminalService(threadService));
|
||||
@@ -88,10 +88,8 @@ export function createApiFactory(threadService: IThreadService, extensionService
|
||||
mainThreadErrors.onUnexpectedExtHostError(errors.transformErrorForSerialization(err));
|
||||
});
|
||||
|
||||
// the converter might create delegate commands to avoid sending args
|
||||
// around all the time
|
||||
ExtHostTypeConverters.Command.initialize(extHostCommands);
|
||||
registerApiCommands(extHostCommands);
|
||||
// Register API-ish commands
|
||||
ExtHostApiCommands.register(extHostCommands);
|
||||
|
||||
// TODO@joh,alex - this is lifecycle critical
|
||||
// fetch and store telemetry info
|
||||
|
||||
@@ -32,7 +32,6 @@ import { MainThreadTerminalService } from './mainThreadTerminalService';
|
||||
import { MainThreadWorkspace } from './mainThreadWorkspace';
|
||||
import { MainProcessExtensionService } from './mainThreadExtensionService';
|
||||
import { MainThreadFileSystemEventService } from './mainThreadFileSystemEventService';
|
||||
import { MainThreadHeapService } from './mainThreadHeapService';
|
||||
|
||||
// --- other interested parties
|
||||
import { MainProcessTextMateSyntax } from 'vs/editor/node/textMate/TMSyntax';
|
||||
@@ -41,6 +40,9 @@ import { JSONValidationExtensionPoint } from 'vs/platform/jsonschemas/common/jso
|
||||
import { LanguageConfigurationFileHandler } from 'vs/editor/node/languageConfiguration';
|
||||
import { SaveParticipant } from './mainThreadSaveParticipant';
|
||||
|
||||
// --- registers itself as service
|
||||
import './mainThreadHeapService';
|
||||
|
||||
export class ExtHostContribution implements IWorkbenchContribution {
|
||||
|
||||
constructor(
|
||||
@@ -89,7 +91,6 @@ export class ExtHostContribution implements IWorkbenchContribution {
|
||||
create(JSONValidationExtensionPoint);
|
||||
create(LanguageConfigurationFileHandler);
|
||||
create(MainThreadFileSystemEventService);
|
||||
create(MainThreadHeapService);
|
||||
create(SaveParticipant);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -276,12 +276,13 @@ export interface ObjectIdentifier {
|
||||
}
|
||||
|
||||
export namespace ObjectIdentifier {
|
||||
export const name = '$ident';
|
||||
export function mixin<T>(obj: T, id: number): T & ObjectIdentifier {
|
||||
Object.defineProperty(obj, '$ident', { value: id, enumerable: true });
|
||||
Object.defineProperty(obj, name, { value: id, enumerable: true });
|
||||
return <T & ObjectIdentifier>obj;
|
||||
}
|
||||
export function get(obj: any): number {
|
||||
return obj['$ident'];
|
||||
export function of(obj: any): number {
|
||||
return obj[name];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -18,16 +18,16 @@ import { IOutline } from 'vs/editor/contrib/quickOpen/common/quickOpen';
|
||||
import { IWorkspaceSymbolProvider, IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search';
|
||||
import { ICodeLensData } from 'vs/editor/contrib/codelens/common/codelens';
|
||||
|
||||
export function registerApiCommands(commands: ExtHostCommands) {
|
||||
new ExtHostApiCommands(commands).registerCommands();
|
||||
}
|
||||
export class ExtHostApiCommands {
|
||||
|
||||
class ExtHostApiCommands {
|
||||
static register(commands: ExtHostCommands) {
|
||||
return new ExtHostApiCommands(commands).registerCommands();
|
||||
}
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _disposables: IDisposable[] = [];
|
||||
|
||||
constructor(commands: ExtHostCommands) {
|
||||
private constructor(commands: ExtHostCommands) {
|
||||
this._commands = commands;
|
||||
}
|
||||
|
||||
@@ -369,7 +369,7 @@ class ExtHostApiCommands {
|
||||
if (!Array.isArray(value)) {
|
||||
return;
|
||||
}
|
||||
return value.map(quickFix => typeConverters.Command.to(quickFix.command));
|
||||
return value.map(quickFix => this._commands.converter.fromInternal(quickFix.command));
|
||||
});
|
||||
}
|
||||
|
||||
@@ -380,7 +380,7 @@ class ExtHostApiCommands {
|
||||
return value.map(item => {
|
||||
return new types.CodeLens(
|
||||
typeConverters.toRange(item.symbol.range),
|
||||
typeConverters.Command.to(item.symbol.command));
|
||||
this._commands.converter.fromInternal(item.symbol.command));
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,7 +12,11 @@ import { ExtHostEditors } from 'vs/workbench/api/node/extHostEditors';
|
||||
import * as extHostTypes from 'vs/workbench/api/node/extHostTypes';
|
||||
import * as extHostTypeConverter from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
import { cloneAndChange } from 'vs/base/common/objects';
|
||||
import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape } from './extHost.protocol';
|
||||
import { MainContext, MainThreadCommandsShape, ExtHostCommandsShape, ObjectIdentifier } from './extHost.protocol';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as vscode from 'vscode';
|
||||
|
||||
interface CommandHandler {
|
||||
callback: Function;
|
||||
@@ -25,14 +29,21 @@ export class ExtHostCommands extends ExtHostCommandsShape {
|
||||
private _commands: { [n: string]: CommandHandler } = Object.create(null);
|
||||
private _proxy: MainThreadCommandsShape;
|
||||
private _extHostEditors: ExtHostEditors;
|
||||
private _converter: CommandsConverter;
|
||||
|
||||
constructor(
|
||||
threadService: IThreadService,
|
||||
extHostEditors: ExtHostEditors
|
||||
extHostEditors: ExtHostEditors,
|
||||
heapService: ExtHostHeapService
|
||||
) {
|
||||
super();
|
||||
this._extHostEditors = extHostEditors;
|
||||
this._proxy = threadService.get(MainContext.MainThreadCommands);
|
||||
this._converter = new CommandsConverter(this, heapService);
|
||||
}
|
||||
|
||||
get converter(): CommandsConverter {
|
||||
return this._converter;
|
||||
}
|
||||
|
||||
registerCommand(id: string, callback: <T>(...args: any[]) => T | Thenable<T>, thisArg?: any, description?: ICommandHandlerDescription): extHostTypes.Disposable {
|
||||
@@ -137,3 +148,68 @@ export class ExtHostCommands extends ExtHostCommandsShape {
|
||||
return TPromise.as(result);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
export class CommandsConverter {
|
||||
|
||||
private _commands: ExtHostCommands;
|
||||
private _heap: ExtHostHeapService;
|
||||
|
||||
// --- conversion between internal and api commands
|
||||
constructor(commands: ExtHostCommands, heap: ExtHostHeapService) {
|
||||
|
||||
this._commands = commands;
|
||||
this._heap = heap;
|
||||
this._commands.registerCommand('_internal_command_delegation', this._executeConvertedCommand, this);
|
||||
}
|
||||
|
||||
toInternal(command: vscode.Command): modes.Command {
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result: modes.Command = {
|
||||
id: command.command,
|
||||
title: command.title
|
||||
};
|
||||
|
||||
if (!isFalsyOrEmpty(command.arguments)) {
|
||||
// we have a contributed command with arguments. that
|
||||
// means we don't want to send the arguments around
|
||||
|
||||
const id = this._heap.keep(command);
|
||||
ObjectIdentifier.mixin(result, id);
|
||||
|
||||
result.id = '_internal_command_delegation';
|
||||
result.arguments = [id];
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
fromInternal(command: modes.Command): vscode.Command {
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
const id = ObjectIdentifier.of(command);
|
||||
if (typeof id === 'number') {
|
||||
return this._heap.get<vscode.Command>(id);
|
||||
|
||||
} else {
|
||||
return {
|
||||
command: command.id,
|
||||
title: command.title,
|
||||
arguments: command.arguments
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
private _executeConvertedCommand([id]: number[]) {
|
||||
const actualCmd = this._heap.get<vscode.Command>(id);
|
||||
return this._commands.executeCommand(actualCmd.command, ...actualCmd.arguments);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -11,19 +11,14 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape {
|
||||
private static _idPool = 0;
|
||||
|
||||
private _data: { [n: number]: any } = Object.create(null);
|
||||
private _callbacks: { [n: number]: Function } = Object.create(null);
|
||||
|
||||
keep(obj: any, callback?: () => any): number {
|
||||
keep(obj: any): number {
|
||||
const id = ExtHostHeapService._idPool++;
|
||||
this._data[id] = obj;
|
||||
if (typeof callback === 'function') {
|
||||
this._callbacks[id] = callback;
|
||||
}
|
||||
return id;
|
||||
}
|
||||
|
||||
delete(id: number): boolean {
|
||||
delete this._callbacks[id];
|
||||
return this._data[id];
|
||||
}
|
||||
|
||||
@@ -33,7 +28,6 @@ export class ExtHostHeapService extends ExtHostHeapServiceShape {
|
||||
|
||||
$onGarbageCollection(ids: number[]): void {
|
||||
for (const id of ids) {
|
||||
setTimeout(this._callbacks[id]);
|
||||
this.delete(id);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
|
||||
import URI from 'vs/base/common/uri';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import * as vscode from 'vscode';
|
||||
import * as TypeConverters from 'vs/workbench/api/node/extHostTypeConverters';
|
||||
@@ -15,10 +14,10 @@ import { IPosition, IRange, ISingleEditOperation } from 'vs/editor/common/editor
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import { ExtHostHeapService } from 'vs/workbench/api/node/extHostHeapService';
|
||||
import { ExtHostDocuments } from 'vs/workbench/api/node/extHostDocuments';
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostCommands, CommandsConverter } from 'vs/workbench/api/node/extHostCommands';
|
||||
import { ExtHostDiagnostics } from 'vs/workbench/api/node/extHostDiagnostics';
|
||||
import { IWorkspaceSymbolProvider, IWorkspaceSymbol } from 'vs/workbench/parts/search/common/search';
|
||||
import { asWinJsPromise, ShallowCancelThenPromise } from 'vs/base/common/async';
|
||||
import { asWinJsPromise } from 'vs/base/common/async';
|
||||
import { MainContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier } from './extHost.protocol';
|
||||
import { regExpLeadsToEndlessLoop } from 'vs/base/common/strings';
|
||||
|
||||
@@ -44,112 +43,57 @@ class OutlineAdapter {
|
||||
}
|
||||
}
|
||||
|
||||
interface CachedCodeLens {
|
||||
symbols: modes.ICodeLensSymbol[];
|
||||
lenses: vscode.CodeLens[];
|
||||
disposables: IDisposable[];
|
||||
}
|
||||
|
||||
class CodeLensAdapter {
|
||||
|
||||
private static _badCmd: vscode.Command = { command: 'missing', title: '<<MISSING COMMAND>>' };
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: ExtHostCommands;
|
||||
private _commands: CommandsConverter;
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _provider: vscode.CodeLensProvider;
|
||||
|
||||
private _cache: { [uri: string]: { version: number; data: TPromise<CachedCodeLens>; } } = Object.create(null);
|
||||
|
||||
constructor(documents: ExtHostDocuments, commands: ExtHostCommands, provider: vscode.CodeLensProvider) {
|
||||
constructor(documents: ExtHostDocuments, commands: CommandsConverter, heapService: ExtHostHeapService, provider: vscode.CodeLensProvider) {
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._heapService = heapService;
|
||||
this._provider = provider;
|
||||
}
|
||||
|
||||
provideCodeLenses(resource: URI): TPromise<modes.ICodeLensSymbol[]> {
|
||||
const doc = this._documents.getDocumentData(resource).document;
|
||||
const version = doc.version;
|
||||
const key = resource.toString();
|
||||
|
||||
// from cache
|
||||
let entry = this._cache[key];
|
||||
if (entry && entry.version === version) {
|
||||
return new ShallowCancelThenPromise(entry.data.then(cached => cached.symbols));
|
||||
}
|
||||
return asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(lenses => {
|
||||
|
||||
const newCodeLensData = asWinJsPromise(token => this._provider.provideCodeLenses(doc, token)).then(lenses => {
|
||||
if (!Array.isArray(lenses)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const data: CachedCodeLens = {
|
||||
lenses,
|
||||
symbols: [],
|
||||
disposables: [],
|
||||
};
|
||||
|
||||
lenses.forEach((lens, i) => {
|
||||
data.symbols.push(<modes.ICodeLensSymbol>{
|
||||
id: String(i),
|
||||
range: TypeConverters.fromRange(lens.range),
|
||||
command: TypeConverters.Command.from(lens.command, data.disposables)
|
||||
if (Array.isArray(lenses)) {
|
||||
return lenses.map(lens => {
|
||||
const id = this._heapService.keep(lens);
|
||||
return ObjectIdentifier.mixin({
|
||||
range: TypeConverters.fromRange(lens.range),
|
||||
command: this._commands.toInternal(lens.command)
|
||||
}, id);
|
||||
});
|
||||
});
|
||||
|
||||
return data;
|
||||
});
|
||||
|
||||
this._cache[key] = {
|
||||
version,
|
||||
data: newCodeLensData
|
||||
};
|
||||
|
||||
return new ShallowCancelThenPromise(newCodeLensData.then(newCached => {
|
||||
if (entry) {
|
||||
// only now dispose old commands et al
|
||||
entry.data.then(oldCached => dispose(oldCached.disposables));
|
||||
}
|
||||
return newCached && newCached.symbols;
|
||||
}));
|
||||
|
||||
});
|
||||
}
|
||||
|
||||
resolveCodeLens(resource: URI, symbol: modes.ICodeLensSymbol): TPromise<modes.ICodeLensSymbol> {
|
||||
|
||||
const entry = this._cache[resource.toString()];
|
||||
if (!entry) {
|
||||
const lens = this._heapService.get<vscode.CodeLens>(ObjectIdentifier.of(symbol));
|
||||
if (!lens) {
|
||||
return;
|
||||
}
|
||||
|
||||
return entry.data.then(cachedData => {
|
||||
let resolve: TPromise<vscode.CodeLens>;
|
||||
if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) {
|
||||
resolve = TPromise.as(lens);
|
||||
} else {
|
||||
resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token));
|
||||
}
|
||||
|
||||
if (!cachedData) {
|
||||
return;
|
||||
}
|
||||
|
||||
let lens = cachedData.lenses[Number(symbol.id)];
|
||||
if (!lens) {
|
||||
return;
|
||||
}
|
||||
|
||||
let resolve: TPromise<vscode.CodeLens>;
|
||||
if (typeof this._provider.resolveCodeLens !== 'function' || lens.isResolved) {
|
||||
resolve = TPromise.as(lens);
|
||||
} else {
|
||||
resolve = asWinJsPromise(token => this._provider.resolveCodeLens(lens, token));
|
||||
}
|
||||
|
||||
return resolve.then(newLens => {
|
||||
lens = newLens || lens;
|
||||
let command = lens.command;
|
||||
if (!command) {
|
||||
command = {
|
||||
title: '<<MISSING COMMAND>>',
|
||||
command: 'missing',
|
||||
};
|
||||
}
|
||||
|
||||
symbol.command = TypeConverters.Command.from(command, cachedData.disposables);
|
||||
return symbol;
|
||||
});
|
||||
return resolve.then(newLens => {
|
||||
newLens = newLens || lens;
|
||||
symbol.command = this._commands.toInternal(newLens.command || CodeLensAdapter._badCmd);
|
||||
return symbol;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -280,13 +224,11 @@ class ReferenceAdapter {
|
||||
class QuickFixAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: ExtHostCommands;
|
||||
private _commands: CommandsConverter;
|
||||
private _diagnostics: ExtHostDiagnostics;
|
||||
private _provider: vscode.CodeActionProvider;
|
||||
|
||||
private _cachedCommands: IDisposable[][] = [];
|
||||
|
||||
constructor(documents: ExtHostDocuments, commands: ExtHostCommands, diagnostics: ExtHostDiagnostics, provider: vscode.CodeActionProvider) {
|
||||
constructor(documents: ExtHostDocuments, commands: CommandsConverter, diagnostics: ExtHostDiagnostics, heapService: ExtHostHeapService, provider: vscode.CodeActionProvider) {
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._diagnostics = diagnostics;
|
||||
@@ -309,23 +251,13 @@ class QuickFixAdapter {
|
||||
}
|
||||
});
|
||||
|
||||
// we cache the last 10 commands that might have been
|
||||
// created during type conversion. when as have more
|
||||
// than 10 we drop the first three
|
||||
const cachedCommands: IDisposable[] = [];
|
||||
if (this._cachedCommands.push(cachedCommands) > 10) {
|
||||
dispose(...this._cachedCommands.shift());
|
||||
dispose(...this._cachedCommands.shift());
|
||||
dispose(...this._cachedCommands.shift());
|
||||
}
|
||||
|
||||
return asWinJsPromise(token => this._provider.provideCodeActions(doc, ran, { diagnostics: allDiagnostics }, token)).then(commands => {
|
||||
if (!Array.isArray(commands)) {
|
||||
return;
|
||||
}
|
||||
return commands.map((command, i) => {
|
||||
return <modes.CodeAction>{
|
||||
command: TypeConverters.Command.from(command, cachedCommands),
|
||||
command: this._commands.toInternal(command),
|
||||
score: i
|
||||
};
|
||||
});
|
||||
@@ -433,7 +365,7 @@ class NavigateTypeAdapter implements IWorkspaceSymbolProvider {
|
||||
return TPromise.as(item);
|
||||
}
|
||||
|
||||
const symbolInfo = this._heapService.get<vscode.SymbolInformation>(ObjectIdentifier.get(item));
|
||||
const symbolInfo = this._heapService.get<vscode.SymbolInformation>(ObjectIdentifier.of(item));
|
||||
if (symbolInfo) {
|
||||
return asWinJsPromise(token => this._provider.resolveWorkspaceSymbol(symbolInfo, token)).then(value => {
|
||||
return value && TypeConverters.fromSymbolInformation(value);
|
||||
@@ -494,12 +426,13 @@ class RenameAdapter {
|
||||
class SuggestAdapter {
|
||||
|
||||
private _documents: ExtHostDocuments;
|
||||
private _commands: CommandsConverter;
|
||||
private _heapService: ExtHostHeapService;
|
||||
private _provider: vscode.CompletionItemProvider;
|
||||
private _disposables: { [id: number]: IDisposable[] } = [];
|
||||
|
||||
constructor(documents: ExtHostDocuments, heapService: ExtHostHeapService, provider: vscode.CompletionItemProvider) {
|
||||
constructor(documents: ExtHostDocuments, commands: CommandsConverter, heapService: ExtHostHeapService, provider: vscode.CompletionItemProvider) {
|
||||
this._documents = documents;
|
||||
this._commands = commands;
|
||||
this._heapService = heapService;
|
||||
this._provider = provider;
|
||||
}
|
||||
@@ -535,11 +468,9 @@ class SuggestAdapter {
|
||||
for (let i = 0; i < list.items.length; i++) {
|
||||
|
||||
const item = list.items[i];
|
||||
const disposables: IDisposable[] = [];
|
||||
const suggestion = TypeConverters.Suggest.from(item, disposables);
|
||||
const id = this._heapService.keep(item, () => dispose(this._disposables[id]));
|
||||
this._disposables[id] = disposables;
|
||||
ObjectIdentifier.mixin(suggestion, id);
|
||||
const suggestion = TypeConverters.Suggest.from(item);
|
||||
suggestion.command = this._commands.toInternal(item.command);
|
||||
ObjectIdentifier.mixin(suggestion, this._heapService.keep(item));
|
||||
|
||||
if (item.textEdit) {
|
||||
|
||||
@@ -577,13 +508,16 @@ class SuggestAdapter {
|
||||
return TPromise.as(suggestion);
|
||||
}
|
||||
|
||||
const id = ObjectIdentifier.get(suggestion);
|
||||
const id = ObjectIdentifier.of(suggestion);
|
||||
const item = this._heapService.get<CompletionItem>(id);
|
||||
if (!item) {
|
||||
return TPromise.as(suggestion);
|
||||
}
|
||||
return asWinJsPromise(token => this._provider.resolveCompletionItem(item, token)).then(resolvedItem => {
|
||||
return TypeConverters.Suggest.from(resolvedItem || item, this._disposables[id]);
|
||||
resolvedItem = resolvedItem || item;
|
||||
const suggestion = TypeConverters.Suggest.from(resolvedItem);
|
||||
suggestion.command = this._commands.toInternal(resolvedItem.command);
|
||||
return suggestion;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -709,7 +643,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape {
|
||||
|
||||
registerCodeLensProvider(selector: vscode.DocumentSelector, provider: vscode.CodeLensProvider): vscode.Disposable {
|
||||
const handle = this._nextHandle();
|
||||
this._adapter[handle] = new CodeLensAdapter(this._documents, this._commands, provider);
|
||||
this._adapter[handle] = new CodeLensAdapter(this._documents, this._commands.converter, this._heapService, provider);
|
||||
this._proxy.$registerCodeLensSupport(handle, selector);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
@@ -778,7 +712,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape {
|
||||
|
||||
registerCodeActionProvider(selector: vscode.DocumentSelector, provider: vscode.CodeActionProvider): vscode.Disposable {
|
||||
const handle = this._nextHandle();
|
||||
this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands, this._diagnostics, provider);
|
||||
this._adapter[handle] = new QuickFixAdapter(this._documents, this._commands.converter, this._diagnostics, this._heapService, provider);
|
||||
this._proxy.$registerQuickFixSupport(handle, selector);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
@@ -856,7 +790,7 @@ export class ExtHostLanguageFeatures extends ExtHostLanguageFeaturesShape {
|
||||
|
||||
registerCompletionItemProvider(selector: vscode.DocumentSelector, provider: vscode.CompletionItemProvider, triggerCharacters: string[]): vscode.Disposable {
|
||||
const handle = this._nextHandle();
|
||||
this._adapter[handle] = new SuggestAdapter(this._documents, this._heapService, provider);
|
||||
this._adapter[handle] = new SuggestAdapter(this._documents, this._commands.converter, this._heapService, provider);
|
||||
this._proxy.$registerSuggestSupport(handle, selector, triggerCharacters);
|
||||
return this._createDisposable(handle);
|
||||
}
|
||||
|
||||
@@ -4,10 +4,7 @@
|
||||
*--------------------------------------------------------------------------------------------*/
|
||||
'use strict';
|
||||
|
||||
import { ExtHostCommands } from 'vs/workbench/api/node/extHostCommands';
|
||||
import Severity from 'vs/base/common/severity';
|
||||
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { stringDiff } from 'vs/base/common/diff/diff';
|
||||
import * as modes from 'vs/editor/common/modes';
|
||||
import * as types from './extHostTypes';
|
||||
@@ -314,7 +311,7 @@ export const CompletionItemKind = {
|
||||
|
||||
export const Suggest = {
|
||||
|
||||
from(item: vscode.CompletionItem, disposables: IDisposable[]): modes.ISuggestion {
|
||||
from(item: vscode.CompletionItem): modes.ISuggestion {
|
||||
const suggestion: modes.ISuggestion = {
|
||||
label: item.label || '<missing label>',
|
||||
insertText: item.insertText || item.label,
|
||||
@@ -323,7 +320,6 @@ export const Suggest = {
|
||||
documentation: item.documentation,
|
||||
sortText: item.sortText,
|
||||
filterText: item.filterText,
|
||||
command: Command.from(item.command, disposables),
|
||||
additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(TextEdit.from)
|
||||
};
|
||||
return suggestion;
|
||||
@@ -375,69 +371,6 @@ export namespace DocumentLink {
|
||||
}
|
||||
}
|
||||
|
||||
export namespace Command {
|
||||
|
||||
const _delegateId = '_internal_delegate_command';
|
||||
const _cache: { [id: string]: vscode.Command } = Object.create(null);
|
||||
let _idPool = 1;
|
||||
|
||||
export function initialize(commands: ExtHostCommands) {
|
||||
return commands.registerCommand(_delegateId, (id: string) => {
|
||||
const command = _cache[id];
|
||||
if (!command) {
|
||||
// handle already disposed delegations graceful
|
||||
return;
|
||||
}
|
||||
return commands.executeCommand(command.command, ...command.arguments);
|
||||
});
|
||||
}
|
||||
|
||||
export function from(command: vscode.Command, disposables: IDisposable[]): modes.Command {
|
||||
|
||||
if (!command) {
|
||||
return;
|
||||
}
|
||||
|
||||
const result = <modes.Command>{
|
||||
id: command.command,
|
||||
title: command.title
|
||||
};
|
||||
|
||||
if (!isFalsyOrEmpty(command.arguments)) {
|
||||
|
||||
// redirect to delegate command and store actual command
|
||||
const id = `delegate/${_idPool++}/for/${command.command}`;
|
||||
|
||||
result.id = _delegateId;
|
||||
result.arguments = [id];
|
||||
_cache[id] = command;
|
||||
|
||||
disposables.push({
|
||||
dispose() {
|
||||
delete _cache[id];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function to(command: modes.Command): vscode.Command {
|
||||
let result: vscode.Command;
|
||||
if (command.id === _delegateId) {
|
||||
let [key] = command.arguments;
|
||||
result = _cache[key];
|
||||
}
|
||||
if (!result) {
|
||||
result = {
|
||||
command: command.id,
|
||||
title: command.title
|
||||
};
|
||||
}
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
export namespace TextDocumentSaveReason {
|
||||
|
||||
export function to(reason: SaveReason): vscode.TextDocumentSaveReason {
|
||||
@@ -451,4 +384,4 @@ export namespace TextDocumentSaveReason {
|
||||
return types.TextDocumentSaveReason.FocusOut;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,28 +5,119 @@
|
||||
|
||||
'use strict';
|
||||
|
||||
import { IDisposable } from 'vs/base/common/lifecycle';
|
||||
import { TPromise } from 'vs/base/common/winjs.base';
|
||||
import { IThreadService } from 'vs/workbench/services/thread/common/threadService';
|
||||
import { ExtHostContext } from './extHost.protocol';
|
||||
import { onDidGarbageCollectSignals, consumeSignals } from 'gc-signals';
|
||||
import { ExtHostContext, ObjectIdentifier } from './extHost.protocol';
|
||||
import { consumeSignals, GCSignal } from 'gc-signals';
|
||||
import { createDecorator } from 'vs/platform/instantiation/common/instantiation';
|
||||
import { registerSingleton } from 'vs/platform/instantiation/common/extensions';
|
||||
|
||||
export class MainThreadHeapService {
|
||||
|
||||
private _subscription: IDisposable;
|
||||
declare class WeakMap<K, V> {
|
||||
set(key: K, value?: V): WeakMap<K, V>;
|
||||
}
|
||||
|
||||
declare class Set<E> {
|
||||
add(e: E): this;
|
||||
has(e: E): boolean;
|
||||
delete(e: E): boolean;
|
||||
}
|
||||
|
||||
export const IHeapService = createDecorator<IHeapService>('heapService');
|
||||
|
||||
export interface IHeapService {
|
||||
_serviceBrand: any;
|
||||
|
||||
/**
|
||||
* Track gc-collection for all new objects that
|
||||
* have the $ident-value set.
|
||||
*/
|
||||
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
|
||||
|
||||
/**
|
||||
* Track gc-collection for all new objects that
|
||||
* have the $ident-value set.
|
||||
*/
|
||||
trackRecursive<T>(obj: T): T;
|
||||
}
|
||||
|
||||
|
||||
export class MainThreadHeapService implements IHeapService {
|
||||
|
||||
_serviceBrand: any;
|
||||
|
||||
private _activeSignals = new WeakMap<any, GCSignal>();
|
||||
private _activeIds = new Set<number>();
|
||||
private _consumeHandle: number;
|
||||
|
||||
constructor( @IThreadService threadService: IThreadService) {
|
||||
const proxy = threadService.get(ExtHostContext.ExtHostHeapService);
|
||||
|
||||
this._subscription = onDidGarbageCollectSignals(ids => {
|
||||
proxy.$onGarbageCollection(ids);
|
||||
});
|
||||
this._consumeHandle = setInterval(() => {
|
||||
const ids = consumeSignals();
|
||||
|
||||
this._consumeHandle = setInterval(consumeSignals, 15 * 1000);
|
||||
if (ids.length > 0) {
|
||||
// local book-keeping
|
||||
for (const id of ids) {
|
||||
this._activeIds.delete(id);
|
||||
}
|
||||
|
||||
// send to ext host
|
||||
proxy.$onGarbageCollection(ids);
|
||||
}
|
||||
|
||||
}, 15 * 1000);
|
||||
}
|
||||
|
||||
dispose() {
|
||||
clearInterval(this._consumeHandle);
|
||||
this._subscription.dispose();
|
||||
}
|
||||
}
|
||||
|
||||
trackRecursive<T>(p: TPromise<T>): TPromise<T>;
|
||||
trackRecursive<T>(obj: T): T;
|
||||
trackRecursive<T>(obj: any): any {
|
||||
if (TPromise.is(obj)) {
|
||||
return obj.then(result => this.trackRecursive(result));
|
||||
} else {
|
||||
return this._doTrackRecursive(obj);
|
||||
}
|
||||
}
|
||||
|
||||
private _doTrackRecursive(obj: any): any {
|
||||
|
||||
const stack = [obj];
|
||||
while (stack.length > 0) {
|
||||
|
||||
// remove first element
|
||||
let obj = stack.shift();
|
||||
|
||||
if (!obj || typeof obj !== 'object') {
|
||||
continue;
|
||||
}
|
||||
|
||||
for (let key in obj) {
|
||||
if (!Object.prototype.hasOwnProperty.call(obj, key)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const value = obj[key];
|
||||
// recurse -> object/array
|
||||
if (typeof value === 'object') {
|
||||
stack.push(value);
|
||||
|
||||
} else if (key === ObjectIdentifier.name) {
|
||||
// track new $ident-objects
|
||||
|
||||
if (typeof value === 'number' && !this._activeIds.has(value)) {
|
||||
this._activeIds.add(value);
|
||||
this._activeSignals.set(obj, new GCSignal(value));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return obj;
|
||||
}
|
||||
}
|
||||
|
||||
registerSingleton(IHeapService, MainThreadHeapService);
|
||||
|
||||
@@ -15,18 +15,23 @@ import { wireCancellationToken } from 'vs/base/common/async';
|
||||
import { CancellationToken } from 'vs/base/common/cancellation';
|
||||
import { Position as EditorPosition } from 'vs/editor/common/core/position';
|
||||
import { Range as EditorRange } from 'vs/editor/common/core/range';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, ObjectIdentifier } from './extHost.protocol';
|
||||
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape } from './extHost.protocol';
|
||||
import { LanguageConfigurationRegistry, LanguageConfiguration } from 'vs/editor/common/modes/languageConfigurationRegistry';
|
||||
import { trackGarbageCollection } from 'gc-signals';
|
||||
import { IHeapService } from './mainThreadHeapService';
|
||||
|
||||
export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape {
|
||||
|
||||
private _proxy: ExtHostLanguageFeaturesShape;
|
||||
private _heapService: IHeapService;
|
||||
private _registrations: { [handle: number]: IDisposable; } = Object.create(null);
|
||||
|
||||
constructor( @IThreadService threadService: IThreadService) {
|
||||
constructor(
|
||||
@IThreadService threadService: IThreadService,
|
||||
@IHeapService heapService: IHeapService
|
||||
) {
|
||||
super();
|
||||
this._proxy = threadService.get(ExtHostContext.ExtHostLanguageFeatures);
|
||||
this._heapService = heapService;
|
||||
}
|
||||
|
||||
$unregister(handle: number): TPromise<any> {
|
||||
@@ -54,10 +59,10 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
|
||||
$registerCodeLensSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.CodeLensProviderRegistry.register(selector, <modes.CodeLensProvider>{
|
||||
provideCodeLenses: (model: IReadOnlyModel, token: CancellationToken): modes.ICodeLensSymbol[] | Thenable<modes.ICodeLensSymbol[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri));
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeLenses(handle, model.uri)));
|
||||
},
|
||||
resolveCodeLens: (model: IReadOnlyModel, codeLens: modes.ICodeLensSymbol, token: CancellationToken): modes.ICodeLensSymbol | Thenable<modes.ICodeLensSymbol> => {
|
||||
return wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens));
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$resolveCodeLens(handle, model.uri, codeLens)));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
@@ -112,7 +117,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
|
||||
$registerQuickFixSupport(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
|
||||
this._registrations[handle] = modes.CodeActionProviderRegistry.register(selector, <modes.CodeActionProvider>{
|
||||
provideCodeActions: (model: IReadOnlyModel, range: EditorRange, token: CancellationToken): Thenable<modes.CodeAction[]> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range));
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCodeActions(handle, model.uri, range)));
|
||||
}
|
||||
});
|
||||
return undefined;
|
||||
@@ -155,14 +160,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
|
||||
$registerNavigateTypeSupport(handle: number): TPromise<any> {
|
||||
this._registrations[handle] = WorkspaceSymbolProviderRegistry.register(<IWorkspaceSymbolProvider>{
|
||||
provideWorkspaceSymbols: (search: string): TPromise<IWorkspaceSymbol[]> => {
|
||||
return this._proxy.$provideWorkspaceSymbols(handle, search).then(result => {
|
||||
if (result) {
|
||||
for (const item of result) {
|
||||
trackGarbageCollection(item, ObjectIdentifier.get(item));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return this._heapService.trackRecursive(this._proxy.$provideWorkspaceSymbols(handle, search));
|
||||
},
|
||||
resolveWorkspaceSymbol: (item: IWorkspaceSymbol): TPromise<IWorkspaceSymbol> => {
|
||||
return this._proxy.$resolveWorkspaceSymbol(handle, item);
|
||||
@@ -188,14 +186,7 @@ export class MainThreadLanguageFeatures extends MainThreadLanguageFeaturesShape
|
||||
this._registrations[handle] = modes.SuggestRegistry.register(selector, <modes.ISuggestSupport>{
|
||||
triggerCharacters: triggerCharacters,
|
||||
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.ISuggestResult> => {
|
||||
return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)).then(result => {
|
||||
if (result && result.suggestions) {
|
||||
for (const suggestion of result.suggestions) {
|
||||
trackGarbageCollection(suggestion, ObjectIdentifier.get(suggestion));
|
||||
}
|
||||
}
|
||||
return result;
|
||||
});
|
||||
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)));
|
||||
},
|
||||
resolveCompletionItem: (model: IReadOnlyModel, position: EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable<modes.ISuggestion> => {
|
||||
return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion));
|
||||
|
||||
Reference in New Issue
Block a user