Refresh master with initial release/0.24 snapshot (#332)

* Initial port of release/0.24 source code

* Fix additional headers

* Fix a typo in launch.json
This commit is contained in:
Karl Burtram
2017-12-15 15:38:57 -08:00
committed by GitHub
parent 271b3a0b82
commit 6ad0df0e3e
7118 changed files with 107999 additions and 56466 deletions

View File

@@ -23,6 +23,7 @@ import './mainThreadCredentials';
// disable the debug service
// import './mainThreadDebugService';
import './mainThreadDecorations';
import './mainThreadDiagnostics';
import './mainThreadDialogs';
import './mainThreadDocumentContentProviders';
@@ -32,6 +33,7 @@ import './mainThreadEditor';
import './mainThreadEditors';
import './mainThreadErrors';
import './mainThreadExtensionService';
import './mainThreadFileSystem';
import './mainThreadFileSystemEventService';
import './mainThreadHeapService';
import './mainThreadLanguageFeatures';

View File

@@ -21,9 +21,7 @@ export class MainThreadCommands implements MainThreadCommandsShape {
extHostContext: IExtHostContext,
@ICommandService private readonly _commandService: ICommandService,
) {
if (extHostContext) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostCommands);
}
this._proxy = extHostContext.get(ExtHostContext.ExtHostCommands);
this._generateCommandsDocumentationRegistration = CommandsRegistry.registerCommand('_generateCommandsDocumentation', () => this._generateCommandsDocumentation());
}

View File

@@ -9,11 +9,11 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { IDisposable } from 'vs/base/common/lifecycle';
import { Registry } from 'vs/platform/registry/common/platform';
import { IConfigurationRegistry, Extensions as ConfigurationExtensions, ConfigurationScope } from 'vs/platform/configuration/common/configurationRegistry';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { IWorkspaceConfigurationService } from 'vs/workbench/services/configuration/common/configuration';
import { IConfigurationEditingService, ConfigurationTarget } from 'vs/workbench/services/configuration/common/configurationEditing';
import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext } from '../node/extHost.protocol';
import { MainThreadConfigurationShape, MainContext, ExtHostContext, IExtHostContext, IWorkspaceConfigurationChangeEventData } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { ConfigurationTarget, IConfigurationChangeEvent } from 'vs/platform/configuration/common/configuration';
@extHostNamedCustomer(MainContext.MainThreadConfiguration)
export class MainThreadConfiguration implements MainThreadConfigurationShape {
@@ -22,14 +22,13 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape {
constructor(
extHostContext: IExtHostContext,
@IConfigurationEditingService private readonly _configurationEditingService: IConfigurationEditingService,
@IWorkspaceContextService private readonly _workspaceContextService: IWorkspaceContextService,
@IWorkspaceConfigurationService configurationService: IWorkspaceConfigurationService
@IWorkspaceConfigurationService private configurationService: IWorkspaceConfigurationService
) {
const proxy = extHostContext.get(ExtHostContext.ExtHostConfiguration);
this._configurationListener = configurationService.onDidUpdateConfiguration(() => {
proxy.$acceptConfigurationChanged(configurationService.getConfigurationData());
this._configurationListener = configurationService.onDidChangeConfiguration(e => {
proxy.$acceptConfigurationChanged(configurationService.getConfigurationData(), this.toConfigurationChangeEventData(e));
});
}
@@ -47,16 +46,26 @@ export class MainThreadConfiguration implements MainThreadConfigurationShape {
private writeConfiguration(target: ConfigurationTarget, key: string, value: any, resource: URI): TPromise<void> {
target = target !== null && target !== undefined ? target : this.deriveConfigurationTarget(key, resource);
return this._configurationEditingService.writeConfiguration(target, { key, value }, { donotNotifyError: true, scopes: { resource } });
return this.configurationService.updateValue(key, value, { resource }, target, true);
}
private deriveConfigurationTarget(key: string, resource: URI): ConfigurationTarget {
if (resource && this._workspaceContextService.hasMultiFolderWorkspace()) {
if (resource && this._workspaceContextService.getWorkbenchState() === WorkbenchState.WORKSPACE) {
const configurationProperties = Registry.as<IConfigurationRegistry>(ConfigurationExtensions.Configuration).getConfigurationProperties();
if (configurationProperties[key] && configurationProperties[key].scope === ConfigurationScope.RESOURCE) {
return ConfigurationTarget.FOLDER;
return ConfigurationTarget.WORKSPACE_FOLDER;
}
}
return ConfigurationTarget.WORKSPACE;
}
private toConfigurationChangeEventData(event: IConfigurationChangeEvent): IWorkspaceConfigurationChangeEventData {
return {
changedConfiguration: event.changedConfiguration,
changedConfigurationByResource: event.changedConfigurationByResource.keys().reduce((result, resource) => {
result[resource.toString()] = event.changedConfigurationByResource.get(resource);
return result;
}, Object.create({}))
};
}
}

View File

@@ -10,8 +10,10 @@ import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { IDebugService, IConfig, IDebugConfigurationProvider } from 'vs/workbench/parts/debug/common/debug';
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ExtHostContext, ExtHostDebugServiceShape, MainThreadDebugServiceShape, DebugSessionUUID, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import severity from 'vs/base/common/severity';
@extHostNamedCustomer(MainContext.MainThreadDebugService)
export class MainThreadDebugService implements MainThreadDebugServiceShape {
@@ -21,7 +23,8 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
constructor(
extHostContext: IExtHostContext,
@IDebugService private debugService: IDebugService
@IDebugService private debugService: IDebugService,
@IWorkspaceContextService private contextService: IWorkspaceContextService,
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostDebugService);
this._toDispose = [];
@@ -36,8 +39,10 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
}));
this._toDispose.push(debugService.onDidCustomEvent(event => {
if (event && event.sessionId) {
const process = this.debugService.findProcessByUUID(event.sessionId);
this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, process.configuration.type, process.configuration.name, event);
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === event.sessionId).pop();
if (process) {
this._proxy.$acceptDebugSessionCustomEvent(event.sessionId, process.configuration.type, process.configuration.name, event);
}
}
}));
}
@@ -52,12 +57,12 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
type: debugType
};
if (hasProvide) {
provider.provideDebugConfigurations = (folder: URI | undefined) => {
provider.provideDebugConfigurations = folder => {
return this._proxy.$provideDebugConfigurations(handle, folder);
};
}
if (hasResolve) {
provider.resolveDebugConfiguration = (folder: URI | undefined, debugConfiguration: any) => {
provider.resolveDebugConfiguration = (folder, debugConfiguration) => {
return this._proxy.$resolveDebugConfiguration(handle, folder, debugConfiguration);
};
}
@@ -71,30 +76,17 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
return TPromise.as<void>(undefined);
}
public $startDebugging(folderUri: URI | undefined, nameOrConfiguration: string | IConfig): TPromise<boolean> {
return this.debugService.startDebugging(folderUri, nameOrConfiguration).then(x => {
public $startDebugging(folderUri: uri | undefined, nameOrConfiguration: string | IConfig): TPromise<boolean> {
const folder = folderUri ? this.contextService.getWorkspace().folders.filter(wf => wf.uri.toString() === folderUri.toString()).pop() : undefined;
return this.debugService.startDebugging(folder, nameOrConfiguration).then(x => {
return true;
}, err => {
return TPromise.wrapError(err && err.message ? err.message : 'cannot start debugging');
});
}
public $startDebugSession(folderUri: URI | undefined, configuration: IConfig): TPromise<DebugSessionUUID> {
if (configuration.request !== 'launch' && configuration.request !== 'attach') {
return TPromise.wrapError(new Error(`only 'launch' or 'attach' allowed for 'request' attribute`));
}
return this.debugService.createProcess(folderUri, configuration).then(process => {
if (process) {
return <DebugSessionUUID>process.getId();
}
return TPromise.wrapError<DebugSessionUUID>(new Error('cannot create debug session'));
}, err => {
return TPromise.wrapError(err && err.message ? err.message : 'cannot start debug session');
});
}
public $customDebugAdapterRequest(sessionId: DebugSessionUUID, request: string, args: any): TPromise<any> {
const process = this.debugService.findProcessByUUID(sessionId);
const process = this.debugService.getModel().getProcesses().filter(p => p.getId() === sessionId).pop();
if (process) {
return process.session.custom(request, args).then(response => {
if (response.success) {
@@ -106,6 +98,12 @@ export class MainThreadDebugService implements MainThreadDebugServiceShape {
}
return TPromise.wrapError(new Error('debug session not found'));
}
public $appendDebugConsole(value: string): TPromise<any> {
// Use warning as severity to get the orange color for messages coming from the debug extension
this.debugService.logToRepl(value, severity.Warning);
return TPromise.as<void>(undefined);
}
}
// {{SQL CARBON EDIT}}
*/

View File

@@ -0,0 +1,68 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainContext, IExtHostContext, MainThreadDecorationsShape, ExtHostDecorationsShape } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IDecorationsService, IDecorationData } from 'vs/workbench/services/decorations/browser/decorations';
@extHostNamedCustomer(MainContext.MainThreadDecorations)
export class MainThreadDecorations implements MainThreadDecorationsShape {
private readonly _provider = new Map<number, [Emitter<URI[]>, IDisposable]>();
private readonly _proxy: ExtHostDecorationsShape;
constructor(
context: IExtHostContext,
@IDecorationsService private readonly _decorationsService: IDecorationsService
) {
this._proxy = context.get(ExtHostContext.ExtHostDecorations);
}
dispose() {
this._provider.forEach(value => dispose(value));
this._provider.clear();
}
$registerDecorationProvider(handle: number, label: string): void {
let emitter = new Emitter<URI[]>();
let registration = this._decorationsService.registerDecorationsProvider({
label,
onDidChange: emitter.event,
provideDecorations: (uri) => {
return this._proxy.$providerDecorations(handle, uri).then(data => {
if (!data) {
return undefined;
}
const [weight, bubble, tooltip, letter, themeColor, source] = data;
return <IDecorationData>{
weight: weight || 0,
bubble: bubble || false,
color: themeColor && themeColor.id,
tooltip,
letter,
source,
};
});
}
});
this._provider.set(handle, [emitter, registration]);
}
$onDidChange(handle: number, resources: URI[]): void {
const [emitter] = this._provider.get(handle);
emitter.fire(resources);
}
$unregisterDecorationProvider(handle: number): void {
if (this._provider.has(handle)) {
dispose(this._provider.get(handle));
this._provider.delete(handle);
}
}
}

View File

@@ -6,9 +6,10 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { isFalsyOrEmpty } from 'vs/base/common/arrays';
import { MainThreadDiaglogsShape, MainContext, IExtHostContext, MainThreadDialogOptions } from '../node/extHost.protocol';
import { MainThreadDiaglogsShape, MainContext, IExtHostContext, MainThreadDialogOpenOptions, MainThreadDialogSaveOptions } from '../node/extHost.protocol';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IWindowService } from 'vs/platform/windows/common/windows';
import { forEach } from 'vs/base/common/collections';
@extHostNamedCustomer(MainContext.MainThreadDialogs)
export class MainThreadDialogs implements MainThreadDiaglogsShape {
@@ -24,40 +25,75 @@ export class MainThreadDialogs implements MainThreadDiaglogsShape {
//
}
$showOpenDialog(options: MainThreadDialogOptions): TPromise<string[]> {
$showOpenDialog(options: MainThreadDialogOpenOptions): TPromise<string[]> {
// TODO@joh what about remote dev setup?
if (options.uri && options.uri.scheme !== 'file') {
return TPromise.wrapError(new Error('bad path'));
if (options.defaultUri && options.defaultUri.scheme !== 'file') {
return TPromise.wrapError(new Error('Not supported - Open-dialogs can only be opened on `file`-uris.'));
}
return new TPromise<string[]>(resolve => {
this._windowService.showOpenDialog(MainThreadDialogs._convertOptions(options), filenames => {
resolve(isFalsyOrEmpty(filenames) ? undefined : filenames);
});
this._windowService.showOpenDialog(
MainThreadDialogs._convertOpenOptions(options),
filenames => resolve(isFalsyOrEmpty(filenames) ? undefined : filenames)
);
});
}
private static _convertOptions(options: MainThreadDialogOptions): Electron.OpenDialogOptions {
$showSaveDialog(options: MainThreadDialogSaveOptions): TPromise<string> {
// TODO@joh what about remote dev setup?
if (options.defaultUri && options.defaultUri.scheme !== 'file') {
return TPromise.wrapError(new Error('Not supported - Save-dialogs can only be opened on `file`-uris.'));
}
return new TPromise<string>(resolve => {
this._windowService.showSaveDialog(
MainThreadDialogs._convertSaveOptions(options),
filename => resolve(!filename ? undefined : filename)
);
});
}
private static _convertOpenOptions(options: MainThreadDialogOpenOptions): Electron.OpenDialogOptions {
const result: Electron.OpenDialogOptions = {
properties: ['createDirectory']
};
if (options.openLabel) {
result.buttonLabel = options.openLabel;
}
if (options.uri) {
result.defaultPath = options.uri.fsPath;
if (options.defaultUri) {
result.defaultPath = options.defaultUri.fsPath;
}
if (!options.openFiles && !options.openFolders) {
options.openFiles = true;
if (!options.canSelectFiles && !options.canSelectFolders) {
options.canSelectFiles = true;
}
if (options.openFiles) {
if (options.canSelectFiles) {
result.properties.push('openFile');
}
if (options.openFolders) {
if (options.canSelectFolders) {
result.properties.push('openDirectory');
}
if (options.openMany) {
if (options.canSelectMany) {
result.properties.push('multiSelections');
}
if (options.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters.push({ name: entry.key, extensions: entry.value }));
}
return result;
}
private static _convertSaveOptions(options: MainThreadDialogSaveOptions): Electron.SaveDialogOptions {
const result: Electron.SaveDialogOptions = {
};
if (options.defaultUri) {
result.defaultPath = options.defaultUri.fsPath;
}
if (options.saveLabel) {
result.buttonLabel = options.saveLabel;
}
if (options.filters) {
result.filters = [];
forEach(options.filters, entry => result.filters.push({ name: entry.key, extensions: entry.value }));
}
return result;
}
}

View File

@@ -6,8 +6,6 @@
import { IModelService } from 'vs/editor/common/services/modelService';
import { IModel, ICommonCodeEditor, isCommonCodeEditor, isCommonDiffEditor } from 'vs/editor/common/editorCommon';
import { compare } from 'vs/base/common/strings';
import { delta } from 'vs/base/common/arrays';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import Event, { Emitter } from 'vs/base/common/event';
@@ -26,28 +24,73 @@ import { IUntitledEditorService } from 'vs/workbench/services/untitled/common/un
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
namespace cmp {
export function compareModels(a: IModel, b: IModel): number {
return compare(a.uri.toString(), b.uri.toString());
namespace mapset {
export function newSet<E>(from: Set<E>): Set<E> {
return new (<any>Set)(from);
// let ret = new Set<E>();
// from.forEach(ret.add, ret);
// return ret;
}
export function compareEditors(a: EditorAndModel, b: EditorAndModel): number {
let ret = compare(a.editor.getId(), b.editor.getId());
if (ret === 0) {
ret = compare(a.document.uri.toString(), b.document.uri.toString());
}
export function setValues<T>(set: Set<T>): T[] {
// return Array.from(set);
let ret: T[] = [];
set.forEach(v => ret.push(v));
return ret;
}
export function mapValues<T>(map: Map<any, T>): T[] {
// return Array.from(map.values());
let ret: T[] = [];
map.forEach(v => ret.push(v));
return ret;
}
}
class EditorAndModel {
namespace delta {
export function ofSets<T>(before: Set<T>, after: Set<T>): { removed: T[], added: T[] } {
const removed: T[] = [];
const added: T[] = [];
before.forEach(element => {
if (!after.has(element)) {
removed.push(element);
}
});
after.forEach(element => {
if (!before.has(element)) {
added.push(element);
}
});
return { removed, added };
};
export function ofMaps<K, V>(before: Map<K, V>, after: Map<K, V>): { removed: V[], added: V[] } {
const removed: V[] = [];
const added: V[] = [];
before.forEach((value, index) => {
if (!after.has(index)) {
removed.push(value);
}
});
after.forEach((value, index) => {
if (!before.has(index)) {
added.push(value);
}
});
return { removed, added };
};
}
class EditorSnapshot {
readonly id: string;
constructor(
readonly editor: ICommonCodeEditor,
readonly document: IModel,
) {
this.id = `${editor.getId()},${document.uri.toString()}`;
this.id = `${editor.getId()},${editor.getModel().id}`;
}
}
@@ -58,8 +101,8 @@ class DocumentAndEditorStateDelta {
constructor(
readonly removedDocuments: IModel[],
readonly addedDocuments: IModel[],
readonly removedEditors: EditorAndModel[],
readonly addedEditors: EditorAndModel[],
readonly removedEditors: EditorSnapshot[],
readonly addedEditors: EditorSnapshot[],
readonly oldActiveEditor: string,
readonly newActiveEditor: string,
) {
@@ -85,10 +128,14 @@ class DocumentAndEditorState {
static compute(before: DocumentAndEditorState, after: DocumentAndEditorState): DocumentAndEditorStateDelta {
if (!before) {
return new DocumentAndEditorStateDelta([], after.documents, [], after.editors, undefined, after.activeEditor);
return new DocumentAndEditorStateDelta(
[], mapset.setValues(after.documents),
[], mapset.mapValues(after.editors),
undefined, after.activeEditor
);
}
const documentDelta = delta(before.documents, after.documents, cmp.compareModels);
const editorDelta = delta(before.editors, after.editors, cmp.compareEditors);
const documentDelta = delta.ofSets(before.documents, after.documents);
const editorDelta = delta.ofMaps(before.editors, after.editors);
const oldActiveEditor = before.activeEditor !== after.activeEditor ? before.activeEditor : undefined;
const newActiveEditor = before.activeEditor !== after.activeEditor ? after.activeEditor : undefined;
@@ -100,12 +147,11 @@ class DocumentAndEditorState {
}
constructor(
readonly documents: IModel[],
readonly editors: EditorAndModel[],
readonly documents: Set<IModel>,
readonly editors: Map<string, EditorSnapshot>,
readonly activeEditor: string,
) {
this.documents = documents.sort(cmp.compareModels);
this.editors = editors.sort(cmp.compareEditors);
//
}
}
@@ -121,7 +167,7 @@ class MainThreadDocumentAndEditorStateComputer {
@ICodeEditorService private _codeEditorService: ICodeEditorService,
@IWorkbenchEditorService private _workbenchEditorService: IWorkbenchEditorService
) {
this._modelService.onModelAdded(this._updateState, this, this._toDispose);
this._modelService.onModelAdded(this._updateStateOnModelAdd, this, this._toDispose);
this._modelService.onModelRemoved(this._updateState, this, this._toDispose);
this._codeEditorService.onCodeEditorAdd(this._onDidAddEditor, this, this._toDispose);
@@ -151,19 +197,45 @@ class MainThreadDocumentAndEditorStateComputer {
}
}
private _updateStateOnModelAdd(model: IModel): void {
if (model.isTooLargeForHavingARichMode()) {
// ignore
return;
}
if (!this._currentState) {
// too early
this._updateState();
return;
}
// small (fast) delta
this._currentState = new DocumentAndEditorState(
this._currentState.documents.add(model),
this._currentState.editors,
this._currentState.activeEditor
);
this._onDidChangeState(new DocumentAndEditorStateDelta(
[], [model],
[], [],
this._currentState.activeEditor, this._currentState.activeEditor
));
}
private _updateState(): void {
// models: ignore too large models
const models = this._modelService.getModels();
for (let i = 0; i < models.length; i++) {
if (models[i].isTooLargeForHavingARichMode()) {
models.splice(i, 1);
i--;
const models = new Set<IModel>();
for (const model of this._modelService.getModels()) {
if (!model.isTooLargeForHavingARichMode()) {
models.add(model);
}
}
// editor: only take those that have a not too large model
const editors: EditorAndModel[] = [];
const editors = new Map<string, EditorSnapshot>();
let activeEditor: string = null;
for (const editor of this._codeEditorService.listCodeEditors()) {
@@ -172,8 +244,8 @@ class MainThreadDocumentAndEditorStateComputer {
&& !model.isDisposed() // model disposed
&& Boolean(this._modelService.getModel(model.uri)) // model disposing, the flag didn't flip yet but the model service already removed it
) {
const apiEditor = new EditorAndModel(editor, model);
editors.push(apiEditor);
const apiEditor = new EditorSnapshot(editor);
editors.set(apiEditor.id, apiEditor);
if (editor.isFocused()) {
activeEditor = apiEditor.id;
}
@@ -194,12 +266,11 @@ class MainThreadDocumentAndEditorStateComputer {
candidate = workbenchEditorControl.getModifiedEditor();
}
if (candidate) {
for (const { editor, id } of editors) {
if (candidate === editor) {
activeEditor = id;
break;
editors.forEach(snapshot => {
if (candidate === snapshot.editor) {
activeEditor = snapshot.id;
}
}
});
}
}
}
@@ -250,7 +321,7 @@ export class MainThreadDocumentsAndEditors {
const mainThreadDocuments = new MainThreadDocuments(this, extHostContext, this._modelService, modeService, this._textFileService, fileService, textModelResolverService, untitledEditorService);
extHostContext.set(MainContext.MainThreadDocuments, mainThreadDocuments);
const mainThreadEditors = new MainThreadEditors(this, extHostContext, codeEditorService, this._workbenchEditorService, editorGroupService, telemetryService);
const mainThreadEditors = new MainThreadEditors(this, extHostContext, codeEditorService, this._workbenchEditorService, editorGroupService, telemetryService, textModelResolverService, fileService, this._modelService);
extHostContext.set(MainContext.MainThreadEditors, mainThreadEditors);
// It is expected that the ctor of the state computer calls our `_onDelta`.
@@ -282,7 +353,7 @@ export class MainThreadDocumentsAndEditors {
// added editors
for (const apiEditor of delta.addedEditors) {
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.document,
const mainThreadEditor = new MainThreadTextEditor(apiEditor.id, apiEditor.editor.getModel(),
apiEditor.editor, { onGainedFocus() { }, onLostFocus() { } }, this._modelService);
this._editors[apiEditor.id] = mainThreadEditor;

View File

@@ -244,6 +244,17 @@ export class MainThreadTextEditor {
this._codeEditor.setDecorations(key, ranges);
}
public setDecorationsFast(key: string, _ranges: number[]): void {
if (!this._codeEditor) {
return;
}
let ranges: Range[] = [];
for (let i = 0, len = Math.floor(_ranges.length / 4); i < len; i++) {
ranges[i] = new Range(_ranges[4 * i], _ranges[4 * i + 1], _ranges[4 * i + 2], _ranges[4 * i + 3]);
}
this._codeEditor.setDecorationsFast(key, ranges);
}
public revealRange(range: IRange, revealType: TextEditorRevealType): void {
if (!this._codeEditor) {
return;

View File

@@ -8,7 +8,7 @@ import URI from 'vs/base/common/uri';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { disposed } from 'vs/base/common/errors';
import { TPromise } from 'vs/base/common/winjs.base';
import { ISingleEditOperation, IDecorationRenderOptions, IDecorationOptions, ILineChange } from 'vs/editor/common/editorCommon';
import { ISingleEditOperation, IDecorationRenderOptions, IDecorationOptions, ILineChange, ICommonCodeEditor, isCommonCodeEditor } from 'vs/editor/common/editorCommon';
import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService';
@@ -18,9 +18,13 @@ import { ITextEditorConfigurationUpdate, TextEditorRevealType, IApplyEditsOption
import { MainThreadDocumentsAndEditors } from './mainThreadDocumentsAndEditors';
import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry';
import { equals as objectEquals } from 'vs/base/common/objects';
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadEditorsShape, ExtHostEditorsShape, ITextDocumentShowOptions, ITextEditorPositionData, IExtHostContext, IWorkspaceResourceEdit } from '../node/extHost.protocol';
import { IRange } from 'vs/editor/common/core/range';
import { ISelection } from 'vs/editor/common/core/selection';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IFileService } from 'vs/platform/files/common/files';
import { bulkEdit, IResourceEdit } from 'vs/editor/common/services/bulkEdit';
import { IModelService } from 'vs/editor/common/services/modelService';
export class MainThreadEditors implements MainThreadEditorsShape {
@@ -31,6 +35,7 @@ export class MainThreadEditors implements MainThreadEditorsShape {
private _toDispose: IDisposable[];
private _textEditorsListenersMap: { [editorId: string]: IDisposable[]; };
private _editorPositionData: ITextEditorPositionData;
private _registeredDecorationTypes: { [decorationType: string]: boolean; };
constructor(
documentsAndEditors: MainThreadDocumentsAndEditors,
@@ -38,7 +43,10 @@ export class MainThreadEditors implements MainThreadEditorsShape {
@ICodeEditorService private _codeEditorService: ICodeEditorService,
@IWorkbenchEditorService workbenchEditorService: IWorkbenchEditorService,
@IEditorGroupService editorGroupService: IEditorGroupService,
@ITelemetryService telemetryService: ITelemetryService
@ITelemetryService telemetryService: ITelemetryService,
@ITextModelService private readonly _textModelResolverService: ITextModelService,
@IFileService private readonly _fileService: IFileService,
@IModelService private readonly _modelService: IModelService,
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostEditors);
this._documentsAndEditors = documentsAndEditors;
@@ -52,7 +60,9 @@ export class MainThreadEditors implements MainThreadEditorsShape {
this._toDispose.push(documentsAndEditors.onTextEditorRemove(editors => editors.forEach(this._onTextEditorRemove, this)));
this._toDispose.push(editorGroupService.onEditorsChanged(() => this._updateActiveAndVisibleTextEditors()));
this._toDispose.push(editorGroupService.onEditorsMoved(() => this._updateActiveAndVisibleTextEditors()));
this._toDispose.push(editorGroupService.onEditorGroupMoved(() => this._updateActiveAndVisibleTextEditors()));
this._registeredDecorationTypes = Object.create(null);
}
public dispose(): void {
@@ -61,6 +71,10 @@ export class MainThreadEditors implements MainThreadEditorsShape {
});
this._textEditorsListenersMap = Object.create(null);
this._toDispose = dispose(this._toDispose);
for (let decorationType in this._registeredDecorationTypes) {
this._codeEditorService.removeDecorationType(decorationType);
}
this._registeredDecorationTypes = Object.create(null);
}
private _onTextEditorAdd(textEditor: MainThreadTextEditor): void {
@@ -126,6 +140,11 @@ export class MainThreadEditors implements MainThreadEditorsShape {
$tryShowEditor(id: string, position: EditorPosition): TPromise<void> {
// check how often this is used
/* __GDPR__
"api.deprecated" : {
"function" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.show' });
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
@@ -141,6 +160,11 @@ export class MainThreadEditors implements MainThreadEditorsShape {
$tryHideEditor(id: string): TPromise<void> {
// check how often this is used
/* __GDPR__
"api.deprecated" : {
"function" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
this._telemetryService.publicLog('api.deprecated', { function: 'TextEditor.hide' });
let mainThreadEditor = this._documentsAndEditors.getEditor(id);
@@ -171,6 +195,14 @@ export class MainThreadEditors implements MainThreadEditorsShape {
return TPromise.as(null);
}
$trySetDecorationsFast(id: string, key: string, ranges: string): TPromise<any> {
if (!this._documentsAndEditors.getEditor(id)) {
return TPromise.wrapError(disposed(`TextEditor(${id})`));
}
this._documentsAndEditors.getEditor(id).setDecorationsFast(key, /*TODO: marshaller is too slow*/JSON.parse(ranges));
return TPromise.as(null);
}
$tryRevealRange(id: string, range: IRange, revealType: TextEditorRevealType): TPromise<any> {
if (!this._documentsAndEditors.getEditor(id)) {
return TPromise.wrapError(disposed(`TextEditor(${id})`));
@@ -194,6 +226,52 @@ export class MainThreadEditors implements MainThreadEditorsShape {
return TPromise.as(this._documentsAndEditors.getEditor(id).applyEdits(modelVersionId, edits, opts));
}
$tryApplyWorkspaceEdit(workspaceResourceEdits: IWorkspaceResourceEdit[]): TPromise<boolean> {
// First check if loaded models were not changed in the meantime
for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) {
const workspaceResourceEdit = workspaceResourceEdits[i];
if (workspaceResourceEdit.modelVersionId) {
let model = this._modelService.getModel(workspaceResourceEdit.resource);
if (model && model.getVersionId() !== workspaceResourceEdit.modelVersionId) {
// model changed in the meantime
return TPromise.as(false);
}
}
}
// Convert to shape expected by bulkEdit below
let resourceEdits: IResourceEdit[] = [];
for (let i = 0, len = workspaceResourceEdits.length; i < len; i++) {
const workspaceResourceEdit = workspaceResourceEdits[i];
const uri = workspaceResourceEdit.resource;
const edits = workspaceResourceEdit.edits;
for (let j = 0, lenJ = edits.length; j < lenJ; j++) {
const edit = edits[j];
resourceEdits.push({
resource: uri,
newText: edit.newText,
newEol: edit.newEol,
range: edit.range
});
}
}
let codeEditor: ICommonCodeEditor;
let editor = this._workbenchEditorService.getActiveEditor();
if (editor) {
let candidate = editor.getControl();
if (isCommonCodeEditor(candidate)) {
codeEditor = candidate;
}
}
return bulkEdit(this._textModelResolverService, codeEditor, resourceEdits, this._fileService)
.then(() => true);
}
$tryInsertSnippet(id: string, template: string, ranges: IRange[], opts: IUndoStopOptions): TPromise<boolean> {
if (!this._documentsAndEditors.getEditor(id)) {
return TPromise.wrapError<boolean>(disposed(`TextEditor(${id})`));
@@ -202,10 +280,12 @@ export class MainThreadEditors implements MainThreadEditorsShape {
}
$registerTextEditorDecorationType(key: string, options: IDecorationRenderOptions): void {
this._registeredDecorationTypes[key] = true;
this._codeEditorService.registerDecorationType(key, options);
}
$removeTextEditorDecorationType(key: string): void {
delete this._registeredDecorationTypes[key];
this._codeEditorService.removeDecorationType(key);
}

View File

@@ -0,0 +1,163 @@
/*---------------------------------------------------------------------------------------------
* Copyright (c) Microsoft Corporation. All rights reserved.
* Licensed under the Source EULA. See License.txt in the project root for license information.
*--------------------------------------------------------------------------------------------*/
'use strict';
import URI from 'vs/base/common/uri';
import { TPromise, PPromise } from 'vs/base/common/winjs.base';
import { ExtHostContext, MainContext, IExtHostContext, MainThreadFileSystemShape, ExtHostFileSystemShape } from '../node/extHost.protocol';
import { IFileService, IFileSystemProvider, IStat, IFileChange } from 'vs/platform/files/common/files';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import Event, { Emitter } from 'vs/base/common/event';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IProgress } from 'vs/platform/progress/common/progress';
import { ISearchResultProvider, ISearchQuery, ISearchComplete, ISearchProgressItem, QueryType, IFileMatch, ISearchService } from 'vs/platform/search/common/search';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
import { onUnexpectedError } from 'vs/base/common/errors';
@extHostNamedCustomer(MainContext.MainThreadFileSystem)
export class MainThreadFileSystem implements MainThreadFileSystemShape {
private readonly _toDispose: IDisposable[] = [];
private readonly _proxy: ExtHostFileSystemShape;
private readonly _provider = new Map<number, RemoteFileSystemProvider>();
constructor(
extHostContext: IExtHostContext,
@IFileService private readonly _fileService: IFileService,
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceEditingService private readonly _workspaceEditingService: IWorkspaceEditingService
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostFileSystem);
}
dispose(): void {
dispose(this._toDispose);
}
$registerFileSystemProvider(handle: number, scheme: string): void {
this._provider.set(handle, new RemoteFileSystemProvider(this._fileService, this._searchService, scheme, handle, this._proxy));
}
$unregisterFileSystemProvider(handle: number): void {
dispose(this._provider.get(handle));
this._provider.delete(handle);
}
$onDidAddFileSystemRoot(uri: URI): void {
this._workspaceEditingService.addFolders([{ uri }], true).done(null, onUnexpectedError);
}
$onFileSystemChange(handle: number, changes: IFileChange[]): void {
this._provider.get(handle).$onFileSystemChange(changes);
}
$reportFileChunk(handle: number, resource: URI, chunk: number[]): void {
this._provider.get(handle).reportFileChunk(resource, chunk);
}
// --- search
$handleSearchProgress(handle: number, session: number, resource: URI): void {
this._provider.get(handle).handleSearchProgress(session, resource);
}
}
class RemoteFileSystemProvider implements IFileSystemProvider, ISearchResultProvider {
private readonly _onDidChange = new Emitter<IFileChange[]>();
private readonly _reads = new Map<string, IProgress<Uint8Array>>();
private readonly _registrations: IDisposable[];
readonly onDidChange: Event<IFileChange[]> = this._onDidChange.event;
constructor(
fileService: IFileService,
searchService: ISearchService,
scheme: string,
private readonly _handle: number,
private readonly _proxy: ExtHostFileSystemShape
) {
this._registrations = [
fileService.registerProvider(scheme, this),
searchService.registerSearchResultProvider(this),
];
}
dispose(): void {
dispose(this._registrations);
this._onDidChange.dispose();
}
$onFileSystemChange(changes: IFileChange[]): void {
this._onDidChange.fire(changes);
}
// --- forwarding calls
utimes(resource: URI, mtime: number, atime: number): TPromise<IStat, any> {
return this._proxy.$utimes(this._handle, resource, mtime, atime);
}
stat(resource: URI): TPromise<IStat, any> {
return this._proxy.$stat(this._handle, resource);
}
read(resource: URI, offset: number, count: number, progress: IProgress<Uint8Array>): TPromise<number, any> {
this._reads.set(resource.toString(), progress);
return this._proxy.$read(this._handle, offset, count, resource);
}
reportFileChunk(resource: URI, chunk: number[]): void {
this._reads.get(resource.toString()).report(Buffer.from(chunk));
}
write(resource: URI, content: Uint8Array): TPromise<void, any> {
return this._proxy.$write(this._handle, resource, [].slice.call(content));
}
unlink(resource: URI): TPromise<void, any> {
return this._proxy.$unlink(this._handle, resource);
}
move(resource: URI, target: URI): TPromise<IStat, any> {
return this._proxy.$move(this._handle, resource, target);
}
mkdir(resource: URI): TPromise<IStat, any> {
return this._proxy.$mkdir(this._handle, resource);
}
readdir(resource: URI): TPromise<[URI, IStat][], any> {
return this._proxy.$readdir(this._handle, resource);
}
rmdir(resource: URI): TPromise<void, any> {
return this._proxy.$rmdir(this._handle, resource);
}
// --- search
private _searches = new Map<number, (resource: URI) => void>();
private _searchesIdPool = 0;
search(query: ISearchQuery): PPromise<ISearchComplete, ISearchProgressItem> {
if (query.type === QueryType.Text) {
return PPromise.as<ISearchComplete>({ results: [], stats: undefined });
}
const id = ++this._searchesIdPool;
const matches: IFileMatch[] = [];
return new PPromise((resolve, reject, report) => {
this._proxy.$fileFiles(this._handle, id, query.filePattern).then(() => {
this._searches.delete(id);
resolve({
results: matches,
stats: undefined
});
}, reject);
this._searches.set(id, resource => {
const match: IFileMatch = { resource };
matches.push(match);
report(match);
});
});
}
handleSearchProgress(session: number, resource: URI): void {
this._searches.get(session)(resource);
}
}

View File

@@ -15,12 +15,11 @@ 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, IRawColorFormatMap, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadLanguageFeaturesShape, ExtHostLanguageFeaturesShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { LanguageConfigurationRegistry } from 'vs/editor/common/modes/languageConfigurationRegistry';
import { LanguageConfiguration } from 'vs/editor/common/modes/languageConfiguration';
import { IHeapService } from './mainThreadHeapService';
import { IModeService } from 'vs/editor/common/services/modeService';
import { ColorFormatter, CombinedColorFormatter } from 'vs/editor/contrib/colorPicker/common/colorFormatter';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadLanguageFeatures)
@@ -30,7 +29,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
private _heapService: IHeapService;
private _modeService: IModeService;
private _registrations: { [handle: number]: IDisposable; } = Object.create(null);
private _formatters: Map<number, ColorFormatter>;
constructor(
extHostContext: IExtHostContext,
@@ -40,7 +38,6 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
this._proxy = extHostContext.get(ExtHostContext.ExtHostLanguageFeatures);
this._heapService = heapService;
this._modeService = modeService;
this._formatters = new Map<number, ColorFormatter>();
}
dispose(): void {
@@ -208,9 +205,17 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- navigate type
$registerNavigateTypeSupport(handle: number): TPromise<any> {
let lastResultId: number;
this._registrations[handle] = WorkspaceSymbolProviderRegistry.register(<IWorkspaceSymbolProvider>{
provideWorkspaceSymbols: (search: string): TPromise<modes.SymbolInformation[]> => {
return this._heapService.trackRecursive(this._proxy.$provideWorkspaceSymbols(handle, search));
return this._proxy.$provideWorkspaceSymbols(handle, search).then(result => {
if (lastResultId !== undefined) {
this._proxy.$releaseWorkspaceSymbols(handle, lastResultId);
}
lastResultId = result._id;
return result.symbols;
});
},
resolveWorkspaceSymbol: (item: modes.SymbolInformation): TPromise<modes.SymbolInformation> => {
return this._proxy.$resolveWorkspaceSymbol(handle, item);
@@ -232,15 +237,25 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
// --- suggest
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[]): TPromise<any> {
$registerSuggestSupport(handle: number, selector: vscode.DocumentSelector, triggerCharacters: string[], supportsResolveDetails: boolean): TPromise<any> {
this._registrations[handle] = modes.SuggestRegistry.register(selector, <modes.ISuggestSupport>{
triggerCharacters,
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return this._heapService.trackRecursive(wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position)));
provideCompletionItems: (model: IReadOnlyModel, position: EditorPosition, context: modes.SuggestContext, token: CancellationToken): Thenable<modes.ISuggestResult> => {
return wireCancellationToken(token, this._proxy.$provideCompletionItems(handle, model.uri, position, context)).then(result => {
if (!result) {
return result;
}
return {
suggestions: result.suggestions,
incomplete: result.incomplete,
dispose: () => this._proxy.$releaseCompletionItems(handle, result._id)
};
});
},
resolveCompletionItem: (model: IReadOnlyModel, position: EditorPosition, suggestion: modes.ISuggestion, token: CancellationToken): Thenable<modes.ISuggestion> => {
return wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion));
}
resolveCompletionItem: supportsResolveDetails
? (model, position, suggestion, token) => wireCancellationToken(token, this._proxy.$resolveCompletionItem(handle, model.uri, position, suggestion))
: undefined
});
return undefined;
}
@@ -279,44 +294,37 @@ export class MainThreadLanguageFeatures implements MainThreadLanguageFeaturesSha
$registerDocumentColorProvider(handle: number, selector: vscode.DocumentSelector): TPromise<any> {
const proxy = this._proxy;
this._registrations[handle] = modes.ColorProviderRegistry.register(selector, <modes.DocumentColorProvider>{
provideColorRanges: (model, token) => {
provideDocumentColors: (model, token) => {
return wireCancellationToken(token, proxy.$provideDocumentColors(handle, model.uri))
.then(documentColors => {
return documentColors.map(documentColor => {
const formatters = documentColor.availableFormats.map(f => {
if (typeof f === 'number') {
return this._formatters.get(f);
} else {
return new CombinedColorFormatter(this._formatters.get(f[0]), this._formatters.get(f[1]));
}
});
const [red, green, blue, alpha] = documentColor.color;
const color = {
red: red / 255.0,
green: green / 255.0,
blue: blue / 255.0,
red: red,
green: green,
blue: blue,
alpha
};
return {
color,
formatters,
range: documentColor.range
};
});
});
},
provideColorPresentations: (model, colorInfo, token) => {
return wireCancellationToken(token, proxy.$provideColorPresentations(handle, model.uri, {
color: [colorInfo.color.red, colorInfo.color.green, colorInfo.color.blue, colorInfo.color.alpha],
range: colorInfo.range
}));
}
});
return TPromise.as(null);
}
$registerColorFormats(formats: IRawColorFormatMap): TPromise<any> {
formats.forEach(f => this._formatters.set(f[0], new ColorFormatter(f[1])));
return TPromise.as(null);
}
// --- configuration
$setLanguageConfiguration(handle: number, languageId: string, _configuration: vscode.LanguageConfiguration): TPromise<any> {

View File

@@ -10,23 +10,38 @@ import URI from 'vs/base/common/uri';
import Event, { Emitter } from 'vs/base/common/event';
import { assign } from 'vs/base/common/objects';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations } from 'vs/workbench/services/scm/common/scm';
import { ISCMService, ISCMRepository, ISCMProvider, ISCMResource, ISCMResourceGroup, ISCMResourceDecorations, ISCMResourceCollection, ISCMResourceSplice } from 'vs/workbench/services/scm/common/scm';
import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation';
import { ICommandService } from 'vs/platform/commands/common/commands';
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResource, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { ExtHostContext, MainThreadSCMShape, ExtHostSCMShape, SCMProviderFeatures, SCMRawResourceSplices, SCMGroupFeatures, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { Command } from 'vs/editor/common/modes';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
class MainThreadSCMResourceCollection implements ISCMResourceCollection {
readonly resources: ISCMResource[] = [];
private _onDidSplice = new Emitter<ISCMResourceSplice>();
readonly onDidSplice = this._onDidSplice.event;
splice(start: number, deleteCount: number, resources: ISCMResource[]) {
this.resources.splice(start, deleteCount, ...resources);
this._onDidSplice.fire({ start, deleteCount, resources });
}
}
class MainThreadSCMResourceGroup implements ISCMResourceGroup {
readonly resourceCollection = new MainThreadSCMResourceCollection();
get hideWhenEmpty(): boolean { return this.features.hideWhenEmpty; }
constructor(
private sourceControlHandle: number,
private handle: number,
public provider: ISCMProvider,
public features: SCMGroupFeatures,
public label: string,
public id: string,
public resources: ISCMResource[]
public id: string
) { }
toJSON(): any {
@@ -41,15 +56,19 @@ class MainThreadSCMResourceGroup implements ISCMResourceGroup {
class MainThreadSCMResource implements ISCMResource {
constructor(
private proxy: ExtHostSCMShape,
private sourceControlHandle: number,
private groupHandle: number,
private handle: number,
public sourceUri: URI,
public command: Command | undefined,
public resourceGroup: ISCMResourceGroup,
public decorations: ISCMResourceDecorations
) { }
open(): TPromise<void> {
return this.proxy.$executeResourceCommand(this.sourceControlHandle, this.groupHandle, this.handle);
}
toJSON(): any {
return {
$mid: 3,
@@ -71,42 +90,41 @@ class MainThreadSCMProvider implements ISCMProvider {
get resources(): ISCMResourceGroup[] {
return this._groups
.filter(g => g.resources.length > 0 || !g.features.hideWhenEmpty);
.filter(g => g.resourceCollection.resources.length > 0 || !g.features.hideWhenEmpty);
}
private _onDidChange = new Emitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
private _onDidChangeResources = new Emitter<void>();
get onDidChangeResources(): Event<void> { return this._onDidChangeResources.event; }
private features: SCMProviderFeatures = {};
get handle(): number { return this._handle; }
get label(): string { return this._label; }
get rootUri(): URI | undefined { return this._rootUri; }
get contextValue(): string { return this._contextValue; }
get commitTemplate(): string | undefined { return this.features.commitTemplate; }
get acceptInputCommand(): Command | undefined { return this.features.acceptInputCommand; }
get statusBarCommands(): Command[] | undefined { return this.features.statusBarCommands; }
get count(): number | undefined { return this.features.count; }
private _onDidChangeCommitTemplate = new Emitter<string>();
get onDidChangeCommitTemplate(): Event<string> { return this._onDidChangeCommitTemplate.event; }
private _count: number | undefined = undefined;
get count(): number | undefined { return this._count; }
private _onDidChange = new Emitter<void>();
get onDidChange(): Event<void> { return this._onDidChange.event; }
constructor(
private proxy: ExtHostSCMShape,
private _handle: number,
private _contextValue: string,
private _label: string,
private _rootUri: URI | undefined,
@ISCMService scmService: ISCMService,
@ICommandService private commandService: ICommandService
) { }
$updateSourceControl(features: SCMProviderFeatures): void {
if ('count' in features) {
this._count = features.count;
}
this.features = assign(this.features, features);
this._onDidChange.fire();
@@ -122,8 +140,7 @@ class MainThreadSCMProvider implements ISCMProvider {
this,
{},
label,
id,
[]
id
);
this._groups.push(group);
@@ -152,37 +169,50 @@ class MainThreadSCMProvider implements ISCMProvider {
this._onDidChange.fire();
}
$updateGroupResourceStates(groupHandle: number, resources: SCMRawResource[]): void {
const group = this._groupsByHandle[groupHandle];
$spliceGroupResourceStates(splices: SCMRawResourceSplices[]): void {
for (const [groupHandle, groupSlices] of splices) {
const group = this._groupsByHandle[groupHandle];
if (!group) {
return;
if (!group) {
console.warn(`SCM group ${groupHandle} not found in provider ${this.label}`);
continue;
}
// reverse the splices sequence in order to apply them correctly
groupSlices.reverse();
for (const [start, deleteCount, rawResources] of groupSlices) {
const resources = rawResources.map(rawResource => {
const [handle, sourceUri, icons, tooltip, strikeThrough, faded, source, letter, color] = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const decorations = {
icon: icon && URI.parse(icon),
iconDark: iconDark && URI.parse(iconDark),
tooltip,
strikeThrough,
faded,
source,
letter,
color: color && color.id
};
return new MainThreadSCMResource(
this.proxy,
this.handle,
groupHandle,
handle,
URI.parse(sourceUri),
group,
decorations
);
});
group.resourceCollection.splice(start, deleteCount, resources);
}
}
group.resources = resources.map(rawResource => {
const [handle, sourceUri, command, icons, tooltip, strikeThrough, faded] = rawResource;
const icon = icons[0];
const iconDark = icons[1] || icon;
const decorations = {
icon: icon && URI.parse(icon),
iconDark: iconDark && URI.parse(iconDark),
tooltip,
strikeThrough,
faded
};
return new MainThreadSCMResource(
this.handle,
groupHandle,
handle,
URI.parse(sourceUri),
command,
group,
decorations
);
});
this._onDidChange.fire();
this._onDidChangeResources.fire();
}
$unregisterGroup(handle: number): void {
@@ -245,8 +275,8 @@ export class MainThreadSCM implements MainThreadSCMShape {
this._disposables = dispose(this._disposables);
}
$registerSourceControl(handle: number, id: string, label: string): void {
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, this.scmService, this.commandService);
$registerSourceControl(handle: number, id: string, label: string, rootUri: string | undefined): void {
const provider = new MainThreadSCMProvider(this._proxy, handle, id, label, rootUri && URI.parse(rootUri), this.scmService, this.commandService);
const repository = this.scmService.registerSCMProvider(provider);
this._repositories[handle] = repository;
@@ -312,7 +342,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
provider.$updateGroupLabel(groupHandle, label);
}
$updateGroupResourceStates(sourceControlHandle: number, groupHandle: number, resources: SCMRawResource[]): void {
$spliceResourceStates(sourceControlHandle: number, splices: SCMRawResourceSplices[]): void {
const repository = this._repositories[sourceControlHandle];
if (!repository) {
@@ -320,7 +350,7 @@ export class MainThreadSCM implements MainThreadSCMShape {
}
const provider = repository.provider as MainThreadSCMProvider;
provider.$updateGroupResourceStates(groupHandle, resources);
provider.$spliceGroupResourceStates(splices);
}
$unregisterGroup(sourceControlHandle: number, handle: number): void {

View File

@@ -24,6 +24,7 @@ import { TextFileEditorModel } from 'vs/workbench/services/textfile/common/textF
import { ExtHostContext, ExtHostDocumentSaveParticipantShape, IExtHostContext } from '../node/extHost.protocol';
import { EditOperation } from 'vs/editor/common/core/editOperation';
import { extHostCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService';
export interface INamedSaveParticpant extends ISaveParticipant {
readonly name: string;
@@ -41,7 +42,7 @@ class TrimWhitespaceParticipant implements INamedSaveParticpant {
}
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
if (this.configurationService.lookup('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) {
if (this.configurationService.getValue('files.trimTrailingWhitespace', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doTrimTrailingWhitespace(model.textEditorModel, env.reason === SaveReason.AUTO);
}
}
@@ -99,7 +100,7 @@ export class FinalNewLineParticipant implements INamedSaveParticpant {
}
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
if (this.configurationService.lookup('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() }).value) {
if (this.configurationService.getValue('files.insertFinalNewline', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doInsertFinalNewLine(model.textEditorModel);
}
}
@@ -127,12 +128,60 @@ export class FinalNewLineParticipant implements INamedSaveParticpant {
}
}
export class TrimFinalNewLinesParticipant implements INamedSaveParticpant {
readonly name = 'TrimFinalNewLinesParticipant';
constructor(
@IConfigurationService private configurationService: IConfigurationService,
@ICodeEditorService private codeEditorService: ICodeEditorService
) {
// Nothing
}
public participate(model: ITextFileEditorModel, env: { reason: SaveReason }): void {
if (this.configurationService.getValue('files.trimFinalNewlines', { overrideIdentifier: model.textEditorModel.getLanguageIdentifier().language, resource: model.getResource() })) {
this.doTrimFinalNewLines(model.textEditorModel);
}
}
private doTrimFinalNewLines(model: IModel): void {
const lineCount = model.getLineCount();
// Do not insert new line if file does not end with new line
if (!lineCount) {
return;
}
let prevSelection: Selection[] = [new Selection(1, 1, 1, 1)];
const editor = findEditor(model, this.codeEditorService);
if (editor) {
prevSelection = editor.getSelections();
}
let currentLineNumber = model.getLineCount();
let currentLine = model.getLineContent(currentLineNumber);
let currentLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(currentLine) === -1;
while (currentLineIsEmptyOrWhitespace) {
currentLineNumber--;
currentLine = model.getLineContent(currentLineNumber);
currentLineIsEmptyOrWhitespace = strings.lastNonWhitespaceIndex(currentLine) === -1;
}
model.pushEditOperations(prevSelection, [EditOperation.delete(new Range(currentLineNumber + 1, 1, lineCount + 1, 1))], edits => prevSelection);
if (editor) {
editor.setSelections(prevSelection);
}
}
}
class FormatOnSaveParticipant implements INamedSaveParticpant {
readonly name = 'FormatOnSaveParticipant';
constructor(
@ICodeEditorService private _editorService: ICodeEditorService,
@IEditorWorkerService private _editorWorkerService: IEditorWorkerService,
@IConfigurationService private _configurationService: IConfigurationService
) {
// Nothing
@@ -142,7 +191,7 @@ class FormatOnSaveParticipant implements INamedSaveParticpant {
const model = editorModel.textEditorModel;
if (env.reason === SaveReason.AUTO
|| !this._configurationService.lookup('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() }).value) {
|| !this._configurationService.getValue('editor.formatOnSave', { overrideIdentifier: model.getLanguageIdentifier().language, resource: editorModel.getResource() })) {
return undefined;
}
@@ -151,7 +200,9 @@ class FormatOnSaveParticipant implements INamedSaveParticpant {
return new TPromise<ISingleEditOperation[]>((resolve, reject) => {
setTimeout(reject, 750);
getDocumentFormattingEdits(model, { tabSize, insertSpaces }).then(resolve, reject);
getDocumentFormattingEdits(model, { tabSize, insertSpaces })
.then(edits => this._editorWorkerService.computeMoreMinimalEdits(model.uri, edits))
.then(resolve, reject);
}).then(edits => {
if (edits && versionNow === model.getVersionId()) {
@@ -230,13 +281,15 @@ export class SaveParticipant implements ISaveParticipant {
@ITelemetryService private _telemetryService: ITelemetryService,
@IInstantiationService instantiationService: IInstantiationService,
@IConfigurationService configurationService: IConfigurationService,
@ICodeEditorService codeEditorService: ICodeEditorService
@ICodeEditorService codeEditorService: ICodeEditorService,
@IEditorWorkerService editorWorkerService: IEditorWorkerService
) {
this._saveParticipants = [
new TrimWhitespaceParticipant(configurationService, codeEditorService),
new FormatOnSaveParticipant(codeEditorService, configurationService),
new FormatOnSaveParticipant(codeEditorService, editorWorkerService, configurationService),
new FinalNewLineParticipant(configurationService, codeEditorService),
new TrimFinalNewLinesParticipant(configurationService, codeEditorService),
new ExtHostSaveParticipant(extHostContext)
];
@@ -266,6 +319,20 @@ export class SaveParticipant implements ISaveParticipant {
});
return sequence(promiseFactory).then(() => {
/* __GDPR__
"saveParticipantStats" : {
"${wildcard}": [
{
"${prefix}": "Success-",
"${classification}": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
},
{
"${prefix}": "Failure-",
"${classification}": { "classification": "SystemMetaData", "purpose": "PerformanceAndHealth" }
}
]
}
*/
this._telemetryService.publicLog('saveParticipantStats', stats);
});
}

View File

@@ -6,6 +6,9 @@
import { TPromise } from 'vs/base/common/winjs.base';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { ContributedTask, ExtensionTaskSourceTransfer } from 'vs/workbench/parts/tasks/common/tasks';
import { ITaskService } from 'vs/workbench/parts/tasks/common/taskService';
import { ExtHostContext, MainThreadTaskShape, ExtHostTaskShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
@@ -19,7 +22,8 @@ export class MainThreadTask implements MainThreadTaskShape {
constructor(
extHostContext: IExtHostContext,
@ITaskService private _taskService: ITaskService
@ITaskService private _taskService: ITaskService,
@IWorkspaceContextService private _workspaceContextServer: IWorkspaceContextService
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostTask);
this._activeHandles = Object.create(null);
@@ -35,7 +39,18 @@ export class MainThreadTask implements MainThreadTaskShape {
public $registerTaskProvider(handle: number): TPromise<void> {
this._taskService.registerTaskProvider(handle, {
provideTasks: () => {
return this._proxy.$provideTasks(handle);
return this._proxy.$provideTasks(handle).then((value) => {
for (let task of value.tasks) {
if (ContributedTask.is(task)) {
let uri = (task._source as any as ExtensionTaskSourceTransfer).__workspaceFolder;
if (uri) {
delete (task._source as any as ExtensionTaskSourceTransfer).__workspaceFolder;
(task._source as any).workspaceFolder = this._workspaceContextServer.getWorkspaceFolder(uri);
}
}
}
return value;
});
}
});
this._activeHandles[handle] = true;

View File

@@ -25,6 +25,11 @@ export class MainThreadTelemetry implements MainThreadTelemetryShape {
}
$publicLog(eventName: string, data: any = Object.create(null)): void {
/* __GDPR__FRAGMENT__
"MainThreadData" : {
"pluginHostTelemetry" : { "classification": "SystemMetaData", "purpose": "FeatureInsight" }
}
*/
data[MainThreadTelemetry._name] = true;
this._telemetryService.publicLog(eventName, data);
}

View File

@@ -33,13 +33,14 @@ export class MainThreadTerminalService implements MainThreadTerminalServiceShape
// when the extension host process goes down ?
}
public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], waitOnExit?: boolean): TPromise<number> {
public $createTerminal(name?: string, shellPath?: string, shellArgs?: string[], env?: { [key: string]: string }, waitOnExit?: boolean): TPromise<number> {
const shellLaunchConfig: IShellLaunchConfig = {
name,
executable: shellPath,
args: shellArgs,
waitOnExit,
ignoreConfigurationCwd: true
ignoreConfigurationCwd: true,
env
};
return TPromise.as(this.terminalService.createInstance(shellLaunchConfig).id);
}

View File

@@ -9,8 +9,8 @@ import { TPromise } from 'vs/base/common/winjs.base';
import { Disposable } from 'vs/base/common/lifecycle';
import { ExtHostContext, MainThreadTreeViewsShape, ExtHostTreeViewsShape, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { IMessageService, Severity } from 'vs/platform/message/common/message';
import { ViewsRegistry } from 'vs/workbench/parts/views/browser/viewsRegistry';
import { ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState } from 'vs/workbench/parts/views/common/views';
import { ViewsRegistry } from 'vs/workbench/browser/parts/views/viewsRegistry';
import { ITreeViewDataProvider, ITreeItem, TreeItemCollapsibleState } from 'vs/workbench/common/views';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
@extHostNamedCustomer(MainContext.MainThreadTreeViews)

View File

@@ -6,20 +6,17 @@
import { isPromiseCanceledError } from 'vs/base/common/errors';
import URI from 'vs/base/common/uri';
import { ISearchService, QueryType, ISearchQuery, ISearchProgressItem, ISearchComplete } from 'vs/platform/search/common/search';
import { IWorkspaceContextService } from 'vs/platform/workspace/common/workspace';
import { IWorkbenchEditorService } from 'vs/workbench/services/editor/common/editorService';
import { ISearchService, QueryType, ISearchQuery, IFolderQuery, ISearchConfiguration } from 'vs/platform/search/common/search';
import { IWorkspaceContextService, WorkbenchState } from 'vs/platform/workspace/common/workspace';
import { ITextFileService } from 'vs/workbench/services/textfile/common/textfiles';
import { ICommonCodeEditor, isCommonCodeEditor } from 'vs/editor/common/editorCommon';
import { bulkEdit, IResourceEdit } from 'vs/editor/common/services/bulkEdit';
import { TPromise, PPromise } from 'vs/base/common/winjs.base';
import { TPromise } from 'vs/base/common/winjs.base';
import { MainThreadWorkspaceShape, ExtHostWorkspaceShape, ExtHostContext, MainContext, IExtHostContext } from '../node/extHost.protocol';
import { ITextModelService } from 'vs/editor/common/services/resolverService';
import { IFileService } from 'vs/platform/files/common/files';
import { IDisposable, dispose, combinedDisposable } from 'vs/base/common/lifecycle';
import { RemoteFileService } from 'vs/workbench/services/files/electron-browser/remoteFileService';
import { Emitter } from 'vs/base/common/event';
import { IDisposable, dispose } from 'vs/base/common/lifecycle';
import { extHostNamedCustomer } from 'vs/workbench/api/electron-browser/extHostCustomers';
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
import { IRelativePattern } from 'vs/base/common/glob';
import { IWorkspaceEditingService } from 'vs/workbench/services/workspace/common/workspaceEditing';
@extHostNamedCustomer(MainContext.MainThreadWorkspace)
export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@@ -33,12 +30,13 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
@ISearchService private readonly _searchService: ISearchService,
@IWorkspaceContextService private readonly _contextService: IWorkspaceContextService,
@ITextFileService private readonly _textFileService: ITextFileService,
@IWorkbenchEditorService private readonly _editorService: IWorkbenchEditorService,
@ITextModelService private readonly _textModelResolverService: ITextModelService,
@IFileService private readonly _fileService: IFileService
@IConfigurationService private _configurationService: IConfigurationService,
@IFileService private readonly _fileService: IFileService,
@IWorkspaceEditingService private _workspaceEditingService: IWorkspaceEditingService
) {
this._proxy = extHostContext.get(ExtHostContext.ExtHostWorkspace);
this._contextService.onDidChangeWorkspaceRoots(this._onDidChangeWorkspace, this, this._toDispose);
this._contextService.onDidChangeWorkspaceFolders(this._onDidChangeWorkspace, this, this._toDispose);
this._contextService.onDidChangeWorkbenchState(this._onDidChangeWorkspace, this, this._toDispose);
}
dispose(): void {
@@ -53,23 +51,42 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
// --- workspace ---
private _onDidChangeWorkspace(): void {
this._proxy.$acceptWorkspaceData(this._contextService.getWorkspace());
this._proxy.$acceptWorkspaceData(this._contextService.getWorkbenchState() === WorkbenchState.EMPTY ? null : this._contextService.getWorkspace());
}
// --- search ---
$startSearch(include: string, exclude: string, maxResults: number, requestId: number): Thenable<URI[]> {
$startSearch(include: string | IRelativePattern, exclude: string | IRelativePattern, maxResults: number, requestId: number): Thenable<URI[]> {
const workspace = this._contextService.getWorkspace();
if (!workspace) {
if (!workspace.folders.length) {
return undefined;
}
let folderQueries: IFolderQuery[];
if (typeof include === 'string' || !include) {
folderQueries = workspace.folders.map(folder => ({ folder: folder.uri })); // absolute pattern: search across all folders
} else {
folderQueries = [{ folder: URI.file(include.base) }]; // relative pattern: search only in base folder
}
const useRipgrep = folderQueries.every(folderQuery => {
const folderConfig = this._configurationService.getConfiguration<ISearchConfiguration>({ resource: folderQuery.folder });
return folderConfig.search.useRipgrep;
});
const ignoreSymlinks = folderQueries.every(folderQuery => {
const folderConfig = this._configurationService.getConfiguration<ISearchConfiguration>({ resource: folderQuery.folder });
return !folderConfig.search.followSymlinks;
});
const query: ISearchQuery = {
folderQueries: workspace.roots.map(root => ({ folder: root })),
folderQueries,
type: QueryType.File,
maxResults,
includePattern: { [include]: true },
excludePattern: { [exclude]: true },
includePattern: { [typeof include === 'string' ? include : !!include ? include.pattern : undefined]: true },
excludePattern: { [typeof exclude === 'string' ? exclude : !!exclude ? exclude.pattern : undefined]: true },
useRipgrep,
ignoreSymlinks
};
this._searchService.extendQuery(query);
@@ -106,96 +123,5 @@ export class MainThreadWorkspace implements MainThreadWorkspaceShape {
return result.results.every(each => each.success === true);
});
}
$applyWorkspaceEdit(edits: IResourceEdit[]): TPromise<boolean> {
let codeEditor: ICommonCodeEditor;
let editor = this._editorService.getActiveEditor();
if (editor) {
let candidate = editor.getControl();
if (isCommonCodeEditor(candidate)) {
codeEditor = candidate;
}
}
return bulkEdit(this._textModelResolverService, codeEditor, edits, this._fileService)
.then(() => true);
}
// --- EXPERIMENT: workspace provider
private _idPool: number = 0;
private readonly _provider = new Map<number, [IDisposable, Emitter<URI>]>();
private readonly _searchSessions = new Map<number, { resolve: (result: ISearchComplete) => void, reject: Function, progress: (item: ISearchProgressItem) => void, matches: URI[] }>();
$registerFileSystemProvider(handle: number, authority: string): void {
if (!(this._fileService instanceof RemoteFileService)) {
throw new Error();
}
const emitter = new Emitter<URI>();
const provider = {
onDidChange: emitter.event,
resolve: (resource) => {
return this._proxy.$resolveFile(handle, resource);
},
update: (resource, value) => {
return this._proxy.$storeFile(handle, resource, value);
}
};
const searchProvider = {
search: (query) => {
if (query.type !== QueryType.File) {
return undefined;
}
const session = ++this._idPool;
return new PPromise<any, any>((resolve, reject, progress) => {
this._searchSessions.set(session, { resolve, reject, progress, matches: [] });
this._proxy.$startSearch(handle, session, query.filePattern);
}, () => {
this._proxy.$cancelSearch(handle, session);
});
}
};
const registrations = combinedDisposable([
this._fileService.registerProvider(authority, provider),
this._searchService.registerSearchResultProvider(searchProvider),
]);
this._provider.set(handle, [registrations, emitter]);
}
$unregisterFileSystemProvider(handle: number): void {
if (this._provider.has(handle)) {
dispose(this._provider.get(handle)[0]);
this._provider.delete(handle);
}
}
$onFileSystemChange(handle: number, resource: URI) {
const [, emitter] = this._provider.get(handle);
emitter.fire(resource);
};
$updateSearchSession(session: number, data: URI): void {
if (this._searchSessions.has(session)) {
this._searchSessions.get(session).progress({ resource: data });
this._searchSessions.get(session).matches.push(data);
}
}
$finishSearchSession(session: number, err?: any): void {
if (this._searchSessions.has(session)) {
const { matches, resolve, reject } = this._searchSessions.get(session);
this._searchSessions.delete(session);
if (err) {
reject(err);
} else {
resolve({
limitHit: false,
stats: undefined,
results: matches.map(resource => ({ resource }))
});
}
}
}
}