diff --git a/.travis.yml b/.travis.yml index 02d0fb00dcc..6bedf740e15 100644 --- a/.travis.yml +++ b/.travis.yml @@ -42,8 +42,8 @@ script: - gulp electron --silent - gulp compile --silent - gulp optimize-vscode --silent - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then ./scripts/test.sh --reporter dot --coverage; else ./scripts/test.sh --reporter dot; fi + - if [[ "$TRAVIS_OS_NAME" == "linux_off" ]]; then ./scripts/test.sh --reporter dot --coverage; else ./scripts/test.sh --reporter dot; fi - ./scripts/test-integration.sh after_success: - - if [[ "$TRAVIS_OS_NAME" == "linux" ]]; then node_modules/.bin/coveralls < .build/coverage/lcov.info; fi \ No newline at end of file + - if [[ "$TRAVIS_OS_NAME" == "linux_off" ]]; then node_modules/.bin/coveralls < .build/coverage/lcov.info; fi \ No newline at end of file diff --git a/build/gulpfile.vscode.js b/build/gulpfile.vscode.js index 404b81bfff5..575635e629d 100644 --- a/build/gulpfile.vscode.js +++ b/build/gulpfile.vscode.js @@ -39,8 +39,8 @@ const nodeModules = ['electron', 'original-fs'] // Build const builtInExtensions = [ - { name: 'ms-vscode.node-debug', version: '1.9.1' }, - { name: 'ms-vscode.node-debug2', version: '1.9.1' } + { name: 'ms-vscode.node-debug', version: '1.9.2' }, + { name: 'ms-vscode.node-debug2', version: '1.9.3' } ]; const vscodeEntryPoints = _.flatten([ diff --git a/extensions/css/client/src/colorDecorators.ts b/extensions/css/client/src/colorDecorators.ts index 5afe66b6c5d..f2c00b2f19e 100644 --- a/extensions/css/client/src/colorDecorators.ts +++ b/extensions/css/client/src/colorDecorators.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument, TextEditor } from 'vscode'; +import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument } from 'vscode'; const MAX_DECORATORS = 500; @@ -63,31 +63,36 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The if (triggerUpdate) { pendingUpdateRequests[documentUriStr] = setTimeout(() => { // check if the document is in use by an active editor - window.visibleTextEditors.forEach(editor => { + for (let editor of window.visibleTextEditors) { if (editor.document && documentUriStr === editor.document.uri.toString()) { - updateDecorationForEditor(editor, documentUriStr); + updateDecorationForEditor(documentUriStr, editor.document.version); + break; } - }); + } delete pendingUpdateRequests[documentUriStr]; }, 500); } } - function updateDecorationForEditor(editor: TextEditor, contentUri: string) { - let document = editor.document; + function updateDecorationForEditor(contentUri: string, documentVersion: number) { decoratorProvider(contentUri).then(ranges => { - let decorations = ranges.slice(0, MAX_DECORATORS).map(range => { - let color = document.getText(range); - return { - range: range, - renderOptions: { - before: { - backgroundColor: color - } - } - }; - }); - editor.setDecorations(colorsDecorationType, decorations); + for (let editor of window.visibleTextEditors) { + let document = editor.document; + if (document && document.version === documentVersion && contentUri === document.uri.toString()) { + let decorations = ranges.slice(0, MAX_DECORATORS).map(range => { + let color = document.getText(range); + return { + range: range, + renderOptions: { + before: { + backgroundColor: color + } + } + }; + }); + editor.setDecorations(colorsDecorationType, decorations); + } + } }); } diff --git a/extensions/html/client/src/colorDecorators.ts b/extensions/html/client/src/colorDecorators.ts index 5afe66b6c5d..f2c00b2f19e 100644 --- a/extensions/html/client/src/colorDecorators.ts +++ b/extensions/html/client/src/colorDecorators.ts @@ -4,7 +4,7 @@ *--------------------------------------------------------------------------------------------*/ 'use strict'; -import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument, TextEditor } from 'vscode'; +import { window, workspace, DecorationOptions, DecorationRenderOptions, Disposable, Range, TextDocument } from 'vscode'; const MAX_DECORATORS = 500; @@ -63,31 +63,36 @@ export function activateColorDecorations(decoratorProvider: (uri: string) => The if (triggerUpdate) { pendingUpdateRequests[documentUriStr] = setTimeout(() => { // check if the document is in use by an active editor - window.visibleTextEditors.forEach(editor => { + for (let editor of window.visibleTextEditors) { if (editor.document && documentUriStr === editor.document.uri.toString()) { - updateDecorationForEditor(editor, documentUriStr); + updateDecorationForEditor(documentUriStr, editor.document.version); + break; } - }); + } delete pendingUpdateRequests[documentUriStr]; }, 500); } } - function updateDecorationForEditor(editor: TextEditor, contentUri: string) { - let document = editor.document; + function updateDecorationForEditor(contentUri: string, documentVersion: number) { decoratorProvider(contentUri).then(ranges => { - let decorations = ranges.slice(0, MAX_DECORATORS).map(range => { - let color = document.getText(range); - return { - range: range, - renderOptions: { - before: { - backgroundColor: color - } - } - }; - }); - editor.setDecorations(colorsDecorationType, decorations); + for (let editor of window.visibleTextEditors) { + let document = editor.document; + if (document && document.version === documentVersion && contentUri === document.uri.toString()) { + let decorations = ranges.slice(0, MAX_DECORATORS).map(range => { + let color = document.getText(range); + return { + range: range, + renderOptions: { + before: { + backgroundColor: color + } + } + }; + }); + editor.setDecorations(colorsDecorationType, decorations); + } + } }); } diff --git a/extensions/html/server/src/htmlServerMain.ts b/extensions/html/server/src/htmlServerMain.ts index 68029c1bf67..a9ab66a1391 100644 --- a/extensions/html/server/src/htmlServerMain.ts +++ b/extensions/html/server/src/htmlServerMain.ts @@ -62,7 +62,7 @@ connection.onInitialize((params: InitializeParams): InitializeResult => { hoverProvider: true, documentHighlightProvider: true, documentRangeFormattingProvider: initializationOptions && initializationOptions['format.enable'], - documentLinkProvider: true, + documentLinkProvider: { resolveProvider: false }, documentSymbolProvider: true, definitionProvider: true, signatureHelpProvider: { triggerCharacters: ['('] }, diff --git a/extensions/markdown/src/extension.ts b/extensions/markdown/src/extension.ts index d5302e0dccd..8dad9e2ec24 100644 --- a/extensions/markdown/src/extension.ts +++ b/extensions/markdown/src/extension.ts @@ -180,6 +180,18 @@ class MDDocumentContentProvider implements vscode.TextDocumentContentProvider { return `
${md.utils.escapeHtml(str)}
`; } }).use(mdnh, {}); + + function addLineNumberRenderer(tokens: any, idx: number, options: any, env: any, self: any) { + const token = tokens[idx]; + if (token.level === 0 && token.map && token.map.length) { + token.attrSet('data-line', token.map[0]); + } + return self.renderToken(tokens, idx, options, env, self); + } + + md.renderer.rules.paragraph_open = addLineNumberRenderer; + md.renderer.rules.heading_open = addLineNumberRenderer; + return md; } diff --git a/extensions/typescript/package.json b/extensions/typescript/package.json index ae1c645c03e..d4d56aba5fb 100644 --- a/extensions/typescript/package.json +++ b/extensions/typescript/package.json @@ -29,6 +29,7 @@ "onLanguage:javascriptreact", "onLanguage:typescript", "onLanguage:typescriptreact", + "onLanguage:jsx-tags", "onCommand:typescript.reloadProjects", "onCommand:javascript.reloadProjects" ], diff --git a/extensions/typescript/src/features/referencesCodeLensProvider.ts b/extensions/typescript/src/features/referencesCodeLensProvider.ts index 145859cecb2..da7a9c55919 100644 --- a/extensions/typescript/src/features/referencesCodeLensProvider.ts +++ b/extensions/typescript/src/features/referencesCodeLensProvider.ts @@ -70,15 +70,19 @@ export default class TypeScriptReferencesCodeLensProvider implements CodeLensPro }; return this.client.execute('references', args, token).then(response => { if (response && response.body) { - const referenceCount = Math.max(0, response.body.refs.length - 1); - const locations = response.body.refs.map(reference => - new Location(Uri.file(reference.file), - new Range( - new Position(reference.start.line - 1, reference.start.offset - 1), - new Position(reference.end.line - 1, reference.end.offset - 1)))); + // Exclude original definition from references + const locations = response.body.refs + .filter(reference => + reference.start.line !== codeLens.range.start.line + 1 + && reference.start.offset !== codeLens.range.start.character + 1) + .map(reference => + new Location(Uri.file(reference.file), + new Range( + new Position(reference.start.line - 1, reference.start.offset - 1), + new Position(reference.end.line - 1, reference.end.offset - 1)))); codeLens.command = { - title: referenceCount + ' ' + (referenceCount === 1 ? localize('oneReferenceLabel', 'reference') : localize('manyReferenceLabel', 'references')), + title: locations.length + ' ' + (locations.length === 1 ? localize('oneReferenceLabel', 'reference') : localize('manyReferenceLabel', 'references')), command: 'editor.action.showReferences', arguments: [codeLens.document, codeLens.range.start, locations] }; @@ -129,7 +133,7 @@ export default class TypeScriptReferencesCodeLensProvider implements CodeLensPro case PConst.Kind.interface: case PConst.Kind.type: case PConst.Kind.enum: - const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${item.text}`, 'g'); + const identifierMatch = new RegExp(`^(.*?(\\b|\\W))${item.text}`, 'gm'); const match = identifierMatch.exec(text); const start = match ? match.index + match[1].length : 0; results.push(new Range( diff --git a/extensions/typescript/src/typescriptMain.ts b/extensions/typescript/src/typescriptMain.ts index 02891af3f94..9b53a6bd255 100644 --- a/extensions/typescript/src/typescriptMain.ts +++ b/extensions/typescript/src/typescriptMain.ts @@ -217,6 +217,23 @@ class LanguageProvider { } ] }); + + const EMPTY_ELEMENTS: string[] = ['area', 'base', 'br', 'col', 'embed', 'hr', 'img', 'input', 'keygen', 'link', 'menuitem', 'meta', 'param', 'source', 'track', 'wbr']; + + languages.setLanguageConfiguration('jsx-tags', { + wordPattern: /(-?\d*\.\d\w*)|([^\`\~\!\@\$\^\&\*\(\)\=\+\[\{\]\}\\\|\;\:\'\"\,\.\<\>\/\s]+)/g, + onEnterRules: [ + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))([_:\\w][_:\\w-.\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + afterText: /^<\/([_:\w][_:\w-.\d]*)\s*>$/i, + action: { indentAction: IndentAction.IndentOutdent } + }, + { + beforeText: new RegExp(`<(?!(?:${EMPTY_ELEMENTS.join('|')}))(\\w[\\w\\d]*)([^/>]*(?!/)>)[^<]*$`, 'i'), + action: { indentAction: IndentAction.Indent } + } + ], + }); }); } diff --git a/npm-shrinkwrap.json b/npm-shrinkwrap.json index e138e426733..87665c3680b 100644 --- a/npm-shrinkwrap.json +++ b/npm-shrinkwrap.json @@ -432,7 +432,7 @@ "xterm": { "version": "2.2.3", "from": "git+https://github.com/Tyriar/xterm.js.git#vscode-release/1.9", - "resolved": "git+https://github.com/Tyriar/xterm.js.git#67eaa7ee4a7a028acc6ef88b01e800c24d534875" + "resolved": "git+https://github.com/Tyriar/xterm.js.git#7fcf6d1bcdaa0df9905673251f5f9336798932e5" }, "yauzl": { "version": "2.3.1", diff --git a/src/vs/editor/common/commonCodeEditor.ts b/src/vs/editor/common/commonCodeEditor.ts index 02ee9023254..c67207a4b8f 100644 --- a/src/vs/editor/common/commonCodeEditor.ts +++ b/src/vs/editor/common/commonCodeEditor.ts @@ -51,6 +51,8 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public readonly onDidFocusEditor: Event = fromEventEmitter(this, editorCommon.EventType.EditorFocus); public readonly onDidBlurEditor: Event = fromEventEmitter(this, editorCommon.EventType.EditorBlur); public readonly onDidDispose: Event = fromEventEmitter(this, editorCommon.EventType.Disposed); + public readonly onWillType: Event = fromEventEmitter(this, editorCommon.EventType.WillType); + public readonly onDidType: Event = fromEventEmitter(this, editorCommon.EventType.DidType); protected domElement: IContextKeyServiceTarget; @@ -569,6 +571,23 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom public trigger(source: string, handlerId: string, payload: any): void { payload = payload || {}; + + // Special case for typing + if (handlerId === editorCommon.Handler.Type) { + if (!this.cursor || typeof payload.text !== 'string' || payload.text.length === 0) { + // nothing to do + return; + } + if (source === 'keyboard') { + this.emit(editorCommon.EventType.WillType, payload.text); + } + this.cursor.trigger(source, handlerId, payload); + if (source === 'keyboard') { + this.emit(editorCommon.EventType.DidType, payload.text); + } + return; + } + let candidate = this.getAction(handlerId); if (candidate !== null) { TPromise.as(candidate.run()).done(null, onUnexpectedError); @@ -712,24 +731,6 @@ export abstract class CommonCodeEditor extends EventEmitter implements editorCom } } - public addTypingListener(character: string, callback: () => void): IDisposable { - if (!this.cursor) { - return { - dispose: () => { - // no-op - } - }; - } - this.cursor.addTypingListener(character, callback); - return { - dispose: () => { - if (this.cursor) { - this.cursor.removeTypingListener(character, callback); - } - } - }; - } - public getLayoutInfo(): editorCommon.EditorLayoutInfo { return this._configuration.editor.layoutInfo; } diff --git a/src/vs/editor/common/controller/cursor.ts b/src/vs/editor/common/controller/cursor.ts index 15e9141dfb0..5d75593dbc9 100644 --- a/src/vs/editor/common/controller/cursor.ts +++ b/src/vs/editor/common/controller/cursor.ts @@ -75,17 +75,11 @@ export class Cursor extends EventEmitter { private modelUnbinds: IDisposable[]; - // Typing listeners - private typingListeners: { - [character: string]: ITypingListener[]; - }; - private cursors: CursorCollection; private cursorUndoStack: ICursorCollectionState[]; private viewModelHelper: IViewModelHelper; private _isHandling: boolean; - private charactersTyped: string; private enableEmptySelectionClipboard: boolean; @@ -108,8 +102,6 @@ export class Cursor extends EventEmitter { this.cursors = new CursorCollection(this.editorId, this.model, this.configuration, this.viewModelHelper); this.cursorUndoStack = []; - this.typingListeners = {}; - this._isHandling = false; this.modelUnbinds = []; @@ -205,25 +197,6 @@ export class Cursor extends EventEmitter { }, 'restoreState', null); } - public addTypingListener(character: string, callback: ITypingListener): void { - if (!this.typingListeners.hasOwnProperty(character)) { - this.typingListeners[character] = []; - } - this.typingListeners[character].push(callback); - } - - public removeTypingListener(character: string, callback: ITypingListener): void { - if (this.typingListeners.hasOwnProperty(character)) { - var listeners = this.typingListeners[character]; - for (var i = 0; i < listeners.length; i++) { - if (listeners[i] === callback) { - listeners.splice(i, 1); - return; - } - } - } - } - private _onModelLanguageChanged(): void { // the mode of this model has changed this.cursors.updateMode(); @@ -310,7 +283,6 @@ export class Cursor extends EventEmitter { private _onHandler(command: string, handler: (ctx: IMultipleCursorOperationContext) => boolean, source: string, data: any): boolean { this._isHandling = true; - this.charactersTyped = ''; var handled = false; @@ -346,22 +318,6 @@ export class Cursor extends EventEmitter { this.cursorUndoStack = []; } - // Ping typing listeners after the model emits events & after I emit events - for (var i = 0; i < this.charactersTyped.length; i++) { - var chr = this.charactersTyped.charAt(i); - if (this.typingListeners.hasOwnProperty(chr)) { - var listeners = this.typingListeners[chr].slice(0); - for (var j = 0, lenJ = listeners.length; j < lenJ; j++) { - // Hoping that listeners understand that the view might be in an awkward state - try { - listeners[j](); - } catch (e) { - onUnexpectedError(e); - } - } - } - } - var newSelections = this.cursors.getSelections(); var newViewSelections = this.cursors.getViewSelections(); @@ -1418,8 +1374,6 @@ export class Cursor extends EventEmitter { chr = text.charAt(i); } - this.charactersTyped += chr; - // Here we must interpret each typed character individually, that's why we create a new context ctx.hasExecutedCommands = this._createAndInterpretHandlerCtx(ctx.eventSource, ctx.eventData, (charHandlerCtx: IMultipleCursorOperationContext) => { diff --git a/src/vs/editor/common/core/characterClassifier.ts b/src/vs/editor/common/core/characterClassifier.ts index e508b6a06c8..bdac75390e3 100644 --- a/src/vs/editor/common/core/characterClassifier.ts +++ b/src/vs/editor/common/core/characterClassifier.ts @@ -56,3 +56,25 @@ export class CharacterClassifier { } } } + +const enum Boolean { + False = 0, + True = 1 +} + +export class CharacterSet { + + private _actual: CharacterClassifier; + + constructor() { + this._actual = new CharacterClassifier(Boolean.False); + } + + public add(charCode: number): void { + this._actual.set(charCode, Boolean.True); + } + + public has(charCode: number): boolean { + return (this._actual.get(charCode) === Boolean.True); + } +} diff --git a/src/vs/editor/common/editorCommon.ts b/src/vs/editor/common/editorCommon.ts index 1a5e11b56df..6fa88c9fd83 100644 --- a/src/vs/editor/common/editorCommon.ts +++ b/src/vs/editor/common/editorCommon.ts @@ -3764,6 +3764,20 @@ export interface ICommonCodeEditor extends IEditor { */ onDidBlurEditor(listener: () => void): IDisposable; + /** + * An event emitted before interpreting typed characters (on the keyboard). + * @event + * @internal + */ + onWillType(listener: (text: string) => void): IDisposable; + + /** + * An event emitted before interpreting typed characters (on the keyboard). + * @event + * @internal + */ + onDidType(listener: (text: string) => void): IDisposable; + /** * Returns true if this editor or one of its widgets has keyboard focus. */ @@ -3905,16 +3919,6 @@ export interface ICommonCodeEditor extends IEditor { * Get the layout info for the editor. */ getLayoutInfo(): EditorLayoutInfo; - - /** - * This listener is notified when a keypress produces a visible character. - * The callback should not do operations on the view, as the view might not be updated to reflect previous typed characters. - * @param character Character to listen to. - * @param callback Function to call when `character` is typed. - * @internal - */ - addTypingListener(character: string, callback: () => void): IDisposable; - } export interface ICommonDiffEditor extends IEditor { @@ -4069,6 +4073,9 @@ export var EventType = { KeyDown: 'keydown', KeyUp: 'keyup', + WillType: 'willType', + DidType: 'didType', + EditorLayout: 'editorLayout', DiffUpdated: 'diffUpdated' diff --git a/src/vs/editor/common/modes.ts b/src/vs/editor/common/modes.ts index 62e5bb8d593..9e4f4bbf394 100644 --- a/src/vs/editor/common/modes.ts +++ b/src/vs/editor/common/modes.ts @@ -214,6 +214,7 @@ export interface ISuggestion { filterText?: string; sortText?: string; noAutoAccept?: boolean; + commitCharacters?: string[]; overwriteBefore?: number; overwriteAfter?: number; additionalTextEdits?: editorCommon.ISingleEditOperation[]; diff --git a/src/vs/editor/contrib/format/common/formatActions.ts b/src/vs/editor/contrib/format/common/formatActions.ts index 8b74b7e3ba2..73737d38288 100644 --- a/src/vs/editor/contrib/format/common/formatActions.ts +++ b/src/vs/editor/contrib/format/common/formatActions.ts @@ -18,6 +18,7 @@ import { EditOperationsCommand } from './formatCommand'; import { CommandsRegistry } from 'vs/platform/commands/common/commands'; import { ICodeEditorService } from 'vs/editor/common/services/codeEditorService'; import { IEditorWorkerService } from 'vs/editor/common/services/editorWorkerService'; +import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; import ModeContextKeys = editorCommon.ModeContextKeys; import EditorContextKeys = editorCommon.EditorContextKeys; @@ -68,9 +69,16 @@ class FormatOnType implements editorCommon.IEditorContribution { } // register typing listeners that will trigger the format - support.autoFormatTriggerCharacters.forEach(ch => { - this.callOnModel.push(this.editor.addTypingListener(ch, this.trigger.bind(this, ch))); - }); + let triggerChars = new CharacterSet(); + for (let ch of support.autoFormatTriggerCharacters) { + triggerChars.add(ch.charCodeAt(0)); + } + this.callOnModel.push(this.editor.onDidType((text: string) => { + let lastCharCode = text.charCodeAt(text.length - 1); + if (triggerChars.has(lastCharCode)) { + this.trigger(String.fromCharCode(lastCharCode)); + } + })); } private trigger(ch: string): void { diff --git a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts index ff24ce99d14..3f7ad0a1b8a 100644 --- a/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts +++ b/src/vs/editor/contrib/parameterHints/browser/parameterHintsWidget.ts @@ -21,6 +21,7 @@ import { ICommonCodeEditor, ICursorSelectionChangedEvent, IConfigurationChangedE import { IContextKey, IContextKeyService } from 'vs/platform/contextkey/common/contextkey'; import { Context, provideSignatureHelp } from '../common/parameterHints'; import { DomScrollableElement } from 'vs/base/browser/ui/scrollbar/scrollableElement'; +import { CharacterSet } from 'vs/editor/common/core/characterClassifier'; const $ = dom.$; @@ -117,22 +118,21 @@ export class ParameterHintsModel extends Disposable { return; } - const allTriggerCharacters: string[] = []; + const triggerChars = new CharacterSet(); for (const support of SignatureHelpProviderRegistry.ordered(model)) { if (Array.isArray(support.signatureHelpTriggerCharacters)) { - allTriggerCharacters.push(...support.signatureHelpTriggerCharacters); + for (const ch of support.signatureHelpTriggerCharacters) { + triggerChars.add(ch.charCodeAt(0)); + } } } - allTriggerCharacters.sort(); - this.triggerCharactersListeners.length = 0; - let lastCh: string; - for (const ch of allTriggerCharacters) { - if (ch !== lastCh) { - lastCh = ch; - this.triggerCharactersListeners.push(this.editor.addTypingListener(ch, () => this.trigger())); + this.triggerCharactersListeners.push(this.editor.onDidType((text: string) => { + let lastCharCode = text.charCodeAt(text.length - 1); + if (triggerChars.has(lastCharCode)) { + this.trigger(); } - } + })); } private onCursorChange(e: ICursorSelectionChangedEvent): void { diff --git a/src/vs/editor/contrib/suggest/browser/suggestController.ts b/src/vs/editor/contrib/suggest/browser/suggestController.ts index 31697b381f3..1b9332f32aa 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestController.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestController.ts @@ -7,6 +7,7 @@ import * as nls from 'vs/nls'; import { KeyCode, KeyMod } from 'vs/base/common/keyCodes'; import { onUnexpectedError } from 'vs/base/common/errors'; +import { isFalsyOrEmpty } from 'vs/base/common/arrays'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; import { ITelemetryService } from 'vs/platform/telemetry/common/telemetry'; @@ -25,6 +26,47 @@ import { SuggestModel } from '../common/suggestModel'; import { ICompletionItem } from '../common/completionModel'; import { SuggestWidget } from './suggestWidget'; +class AcceptOnCharacterOracle { + + private _disposables: IDisposable[] = []; + + private _activeAcceptCharacters = new Set(); + private _activeItem: ICompletionItem; + + constructor(editor: ICodeEditor, widget: SuggestWidget, accept: (item: ICompletionItem) => any) { + + this._disposables.push(widget.onDidFocus(item => { + if (!item || isFalsyOrEmpty(item.suggestion.commitCharacters)) { + this._activeItem = undefined; + return; + } + + this._activeItem = item; + this._activeAcceptCharacters.clear(); + for (const ch of item.suggestion.commitCharacters) { + this._activeAcceptCharacters.add(ch[0]); + } + })); + + this._disposables.push(editor.onWillType(text => { + if (this._activeItem) { + const ch = text[text.length - 1]; + if (this._activeAcceptCharacters.has(ch)) { + accept(this._activeItem); + } + } + })); + } + + reset(): void { + this._activeItem = undefined; + } + + dispose() { + dispose(this._disposables); + } +} + @editorContribution export class SuggestController implements IEditorContribution { private static ID: string = 'editor.contrib.suggestController'; @@ -59,6 +101,14 @@ export class SuggestController implements IEditorContribution { this.widget = instantiationService.createInstance(SuggestWidget, this.editor); this.toDispose.push(this.widget.onDidSelect(this.onDidSelectItem, this)); + + // Wire up logic to accept a suggestion on certain characters + const autoAcceptOracle = new AcceptOnCharacterOracle(editor, this.widget, item => this.onDidSelectItem(item)); + this.toDispose.push( + this.model.onDidCancel(autoAcceptOracle.reset, autoAcceptOracle), + this.model.onDidTrigger(autoAcceptOracle.reset, autoAcceptOracle), + autoAcceptOracle + ); } getId(): string { diff --git a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts index b9ec550e7eb..6353559e052 100644 --- a/src/vs/editor/contrib/suggest/browser/suggestWidget.ts +++ b/src/vs/editor/contrib/suggest/browser/suggestWidget.ts @@ -293,7 +293,7 @@ export class SuggestWidget implements IContentWidget, IDelegate static NO_SUGGESTIONS_MESSAGE: string = nls.localize('suggestWidget.noSuggestions', "No suggestions."); // Editor.IContentWidget.allowEditorOverflow - allowEditorOverflow = true; + readonly allowEditorOverflow = true; private state: State; private isAuto: boolean; @@ -312,12 +312,16 @@ export class SuggestWidget implements IContentWidget, IDelegate private suggestWidgetMultipleSuggestions: IContextKey; private suggestionSupportsAutoAccept: IContextKey; - private onDidSelectEmitter = new Emitter(); - private editorBlurTimeout: TPromise; private showTimeout: TPromise; private toDispose: IDisposable[]; + private onDidSelectEmitter = new Emitter(); + private onDidFocusEmitter = new Emitter(); + + readonly onDidSelect: Event = this.onDidSelectEmitter.event; + readonly onDidFocus: Event = this.onDidFocusEmitter.event; + constructor( private editor: ICodeEditor, @ITelemetryService private telemetryService: ITelemetryService, @@ -472,6 +476,9 @@ export class SuggestWidget implements IContentWidget, IDelegate }) .then(null, err => !isPromiseCanceledError(err) && onUnexpectedError(err)) .then(() => this.currentSuggestionDetails = null); + + // emit an event + this.onDidFocusEmitter.fire(item); } private setState(state: State): void { @@ -528,10 +535,6 @@ export class SuggestWidget implements IContentWidget, IDelegate } } - get onDidSelect(): Event { - return this.onDidSelectEmitter.event; - } - showTriggered(auto: boolean) { if (this.state !== State.Hidden) { return; diff --git a/src/vs/editor/contrib/suggest/common/suggestModel.ts b/src/vs/editor/contrib/suggest/common/suggestModel.ts index 5a3f163ea12..f9c51e51cf9 100644 --- a/src/vs/editor/contrib/suggest/common/suggestModel.ts +++ b/src/vs/editor/contrib/suggest/common/suggestModel.ts @@ -6,7 +6,6 @@ import { onUnexpectedError } from 'vs/base/common/errors'; import { isFalsyOrEmpty } from 'vs/base/common/arrays'; -import { forEach } from 'vs/base/common/collections'; import Event, { Emitter } from 'vs/base/common/event'; import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { TPromise } from 'vs/base/common/winjs.base'; @@ -87,7 +86,7 @@ export class SuggestModel implements IDisposable { private toDispose: IDisposable[] = []; private quickSuggestDelay: number; - private triggerCharacterListeners: IDisposable[] = []; + private triggerCharacterListener: IDisposable; private triggerAutoSuggestPromise: TPromise; private state: State; @@ -139,9 +138,8 @@ export class SuggestModel implements IDisposable { } dispose(): void { - dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger]); + dispose([this._onDidCancel, this._onDidSuggest, this._onDidTrigger, this.triggerCharacterListener]); this.toDispose = dispose(this.toDispose); - this.triggerCharacterListeners = dispose(this.triggerCharacterListeners); this.cancel(); } @@ -157,7 +155,7 @@ export class SuggestModel implements IDisposable { private updateTriggerCharacters(): void { - this.triggerCharacterListeners = dispose(this.triggerCharacterListeners); + dispose(this.triggerCharacterListener); if (this.editor.getConfiguration().readOnly || !this.editor.getModel() @@ -181,10 +179,12 @@ export class SuggestModel implements IDisposable { } } - forEach(supportsByTriggerCharacter, entry => { - this.triggerCharacterListeners.push(this.editor.addTypingListener(entry.key, () => { - this.trigger(true, false, entry.value); - })); + this.triggerCharacterListener = this.editor.onDidType((text: string) => { + let lastChar = text.charAt(text.length - 1); + let supports = supportsByTriggerCharacter[lastChar]; + if (supports) { + this.trigger(true, false, supports); + } }); } diff --git a/src/vs/platform/extensionManagement/node/extensionManagementService.ts b/src/vs/platform/extensionManagement/node/extensionManagementService.ts index 1e5d0033a8c..470459a4f4d 100644 --- a/src/vs/platform/extensionManagement/node/extensionManagementService.ts +++ b/src/vs/platform/extensionManagement/node/extensionManagementService.ts @@ -171,7 +171,7 @@ export class ExtensionManagementService implements IExtensionManagementService { nls.localize('install', "Yes"), nls.localize('doNotInstall', "No") ]; - return this.choiceService.choose(Severity.Info, message, options) + return this.choiceService.choose(Severity.Info, message, options, true) .then(value => { if (value === 0) { return this.installWithDependencies(compatibleVersion); diff --git a/src/vs/vscode.d.ts b/src/vs/vscode.d.ts index b314111ceea..cbd1ad7f30c 100644 --- a/src/vs/vscode.d.ts +++ b/src/vs/vscode.d.ts @@ -2396,6 +2396,13 @@ declare module 'vscode' { */ range?: Range; + /** + * An optional set of characters that when pressed while this completion is active will accept it first and + * then type that character. *Note* that all commit characters should have `length=1` and that superfluous + * characters will be ignored. + */ + commitCharacters?: string[]; + /** * @deprecated **Deprecated** in favor of `CompletionItem.insertText` and `CompletionItem.range`. * @@ -2814,7 +2821,7 @@ declare module 'vscode' { /** * Readable dictionary that backs this configuration. */ - readonly [key: string]: any; + readonly[key: string]: any; } /** diff --git a/src/vs/workbench/api/node/extHost.api.impl.ts b/src/vs/workbench/api/node/extHost.api.impl.ts index 0a37626f71b..1bbd071eb1a 100644 --- a/src/vs/workbench/api/node/extHost.api.impl.ts +++ b/src/vs/workbench/api/node/extHost.api.impl.ts @@ -96,7 +96,7 @@ export function createApiFactory(initData: IInitData, threadService: IThreadServ if (extension.enableProposedApi) { - if (!initData.environment.enableProposedApi) { + if (!initData.environment.enableProposedApi && !extension.isBuiltin) { extension.enableProposedApi = false; console.warn('PROPOSED API is only available when developing an extension'); diff --git a/src/vs/workbench/api/node/extHostLanguageFeatures.ts b/src/vs/workbench/api/node/extHostLanguageFeatures.ts index 38706f8abb2..6128e8165c2 100644 --- a/src/vs/workbench/api/node/extHostLanguageFeatures.ts +++ b/src/vs/workbench/api/node/extHostLanguageFeatures.ts @@ -514,7 +514,8 @@ class SuggestAdapter { // insertText: undefined, additionalTextEdits: item.additionalTextEdits && item.additionalTextEdits.map(TypeConverters.TextEdit.from), - command: this._commands.toInternal(item.command) + command: this._commands.toInternal(item.command), + commitCharacters: item.commitCharacters }; // 'insertText'-logic diff --git a/src/vs/workbench/api/node/mainThreadOutputService.ts b/src/vs/workbench/api/node/mainThreadOutputService.ts index 313e7c28dd1..bbaacb49b47 100644 --- a/src/vs/workbench/api/node/mainThreadOutputService.ts +++ b/src/vs/workbench/api/node/mainThreadOutputService.ts @@ -53,7 +53,7 @@ export class MainThreadOutputService extends MainThreadOutputServiceShape { public $close(channelId: string): TPromise { const panel = this._panelService.getActivePanel(); if (panel && panel.getId() === OUTPUT_PANEL_ID && channelId === this._outputService.getActiveChannel().id) { - this._partService.setPanelHidden(true); + return this._partService.setPanelHidden(true); } return undefined; diff --git a/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts b/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts index e31853b1973..a5d1f667ac7 100644 --- a/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts +++ b/src/vs/workbench/browser/actions/toggleSidebarVisibility.ts @@ -30,9 +30,7 @@ export class ToggleSidebarVisibilityAction extends Action { public run(): TPromise { const hideSidebar = this.partService.isVisible(Parts.SIDEBAR_PART); - this.partService.setSideBarHidden(hideSidebar); - - return TPromise.as(null); + return this.partService.setSideBarHidden(hideSidebar); } } diff --git a/src/vs/workbench/browser/layout.ts b/src/vs/workbench/browser/layout.ts index 09ac060b7f4..cd74b564aaf 100644 --- a/src/vs/workbench/browser/layout.ts +++ b/src/vs/workbench/browser/layout.ts @@ -5,6 +5,8 @@ 'use strict'; import { Dimension, Builder } from 'vs/base/browser/builder'; +import { TPromise } from 'vs/base/common/winjs.base'; +import * as errors from 'vs/base/common/errors'; import { Part } from 'vs/workbench/browser/part'; import { QuickOpenController } from 'vs/workbench/browser/parts/quickopen/quickOpenController'; import { Sash, ISashEvent, IVerticalSashLayoutProvider, IHorizontalSashLayoutProvider, Orientation } from 'vs/base/browser/ui/sash/sash'; @@ -18,15 +20,18 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { IEditorGroupService } from 'vs/workbench/services/group/common/groupService'; import { getZoomFactor } from 'vs/base/browser/browser'; -const DEFAULT_MIN_SIDEBAR_PART_WIDTH = 170; -const DEFAULT_MIN_PANEL_PART_HEIGHT = 77; -const DEFAULT_MIN_EDITOR_PART_HEIGHT = 70; -const DEFAULT_MIN_EDITOR_PART_WIDTH = 220; +const MIN_SIDEBAR_PART_WIDTH = 170; +const MIN_EDITOR_PART_HEIGHT = 70; +const MIN_EDITOR_PART_WIDTH = 220; +const MIN_PANEL_PART_HEIGHT = 77; const DEFAULT_PANEL_HEIGHT_COEFFICIENT = 0.4; const HIDE_SIDEBAR_WIDTH_THRESHOLD = 50; const HIDE_PANEL_HEIGHT_THRESHOLD = 50; +const TITLE_BAR_HEIGHT = 22; +const STATUS_BAR_HEIGHT = 22; +const ACTIVITY_BAR_WIDTH = 50; -interface ComputedStyles { +interface PartLayoutInfo { titlebar: { height: number; }; activitybar: { width: number; }; sidebar: { minWidth: number; }; @@ -53,8 +58,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal private statusbar: Part; private quickopen: QuickOpenController; private toUnbind: IDisposable[]; - private computedStyles: ComputedStyles; - private initialComputedStyles: ComputedStyles; + private partLayoutInfo: PartLayoutInfo; private workbenchSize: Dimension; private sashX: Sash; private sashY: Sash; @@ -101,7 +105,7 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal this.statusbar = parts.statusbar; this.quickopen = quickopen; this.toUnbind = []; - this.computedStyles = null; + this.partLayoutInfo = this.getPartLayoutInfo(); this.panelHeightBeforeMaximized = 0; this.sashX = new Sash(this.workbenchContainer.getHTMLElement(), this, { @@ -118,13 +122,37 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal this.layoutEditorGroupsVertically = (this.editorGroupService.getGroupOrientation() !== 'horizontal'); - this.toUnbind.push(themeService.onDidColorThemeChange(_ => this.relayout())); + this.toUnbind.push(themeService.onDidColorThemeChange(_ => this.layout())); this.toUnbind.push(editorGroupService.onEditorsChanged(() => this.onEditorsChanged())); this.toUnbind.push(editorGroupService.onGroupOrientationChanged(e => this.onGroupOrientationChanged())); this.registerSashListeners(); } + private getPartLayoutInfo(): PartLayoutInfo { + return { + titlebar: { + height: TITLE_BAR_HEIGHT + }, + activitybar: { + width: ACTIVITY_BAR_WIDTH + }, + sidebar: { + minWidth: MIN_SIDEBAR_PART_WIDTH + }, + panel: { + minHeight: MIN_PANEL_PART_HEIGHT + }, + editor: { + minWidth: MIN_EDITOR_PART_WIDTH, + minHeight: MIN_EDITOR_PART_HEIGHT + }, + statusbar: { + height: STATUS_BAR_HEIGHT + } + }; + } + private registerSashListeners(): void { let startX: number = 0; let startY: number = 0; @@ -144,37 +172,38 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal let sidebarPosition = this.partService.getSideBarPosition(); let isSidebarVisible = this.partService.isVisible(Parts.SIDEBAR_PART); let newSashWidth = (sidebarPosition === Position.LEFT) ? this.startSidebarWidth + e.currentX - startX : this.startSidebarWidth - e.currentX + startX; + let promise = TPromise.as(null); // Sidebar visible if (isSidebarVisible) { // Automatically hide side bar when a certain threshold is met - if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.computedStyles.sidebar.minWidth) { - let dragCompensation = DEFAULT_MIN_SIDEBAR_PART_WIDTH - HIDE_SIDEBAR_WIDTH_THRESHOLD; - this.partService.setSideBarHidden(true); + if (newSashWidth + HIDE_SIDEBAR_WIDTH_THRESHOLD < this.partLayoutInfo.sidebar.minWidth) { + let dragCompensation = MIN_SIDEBAR_PART_WIDTH - HIDE_SIDEBAR_WIDTH_THRESHOLD; + promise = this.partService.setSideBarHidden(true); startX = (sidebarPosition === Position.LEFT) ? Math.max(this.activitybarWidth, e.currentX - dragCompensation) : Math.min(e.currentX + dragCompensation, this.workbenchSize.width - this.activitybarWidth); this.sidebarWidth = this.startSidebarWidth; // when restoring sidebar, restore to the sidebar width we started from } // Otherwise size the sidebar accordingly else { - this.sidebarWidth = Math.max(this.computedStyles.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH - doLayout = newSashWidth >= this.computedStyles.sidebar.minWidth; + this.sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, newSashWidth); // Sidebar can not become smaller than MIN_PART_WIDTH + doLayout = newSashWidth >= this.partLayoutInfo.sidebar.minWidth; } } // Sidebar hidden else { - if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.computedStyles.sidebar.minWidth) || - (sidebarPosition === Position.RIGHT && startX - e.currentX >= this.computedStyles.sidebar.minWidth)) { - this.startSidebarWidth = this.computedStyles.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX); - this.sidebarWidth = this.computedStyles.sidebar.minWidth; - this.partService.setSideBarHidden(false); + if ((sidebarPosition === Position.LEFT && e.currentX - startX >= this.partLayoutInfo.sidebar.minWidth) || + (sidebarPosition === Position.RIGHT && startX - e.currentX >= this.partLayoutInfo.sidebar.minWidth)) { + this.startSidebarWidth = this.partLayoutInfo.sidebar.minWidth - (sidebarPosition === Position.LEFT ? e.currentX - startX : startX - e.currentX); + this.sidebarWidth = this.partLayoutInfo.sidebar.minWidth; + promise = this.partService.setSideBarHidden(false); } } if (doLayout) { - this.layout(); + promise.done(() => this.layout(), errors.onUnexpectedError); } }); @@ -182,36 +211,37 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal let doLayout = false; let isPanelVisible = this.partService.isVisible(Parts.PANEL_PART); let newSashHeight = this.startPanelHeight - (e.currentY - startY); + let promise = TPromise.as(null); // Panel visible if (isPanelVisible) { // Automatically hide panel when a certain threshold is met - if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.computedStyles.panel.minHeight) { - let dragCompensation = DEFAULT_MIN_PANEL_PART_HEIGHT - HIDE_PANEL_HEIGHT_THRESHOLD; - this.partService.setPanelHidden(true); + if (newSashHeight + HIDE_PANEL_HEIGHT_THRESHOLD < this.partLayoutInfo.panel.minHeight) { + let dragCompensation = MIN_PANEL_PART_HEIGHT - HIDE_PANEL_HEIGHT_THRESHOLD; + promise = this.partService.setPanelHidden(true); startY = Math.min(this.sidebarHeight - this.statusbarHeight - this.titlebarHeight, e.currentY + dragCompensation); this.panelHeight = this.startPanelHeight; // when restoring panel, restore to the panel height we started from } // Otherwise size the panel accordingly else { - this.panelHeight = Math.max(this.computedStyles.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT - doLayout = newSashHeight >= this.computedStyles.panel.minHeight; + this.panelHeight = Math.max(this.partLayoutInfo.panel.minHeight, newSashHeight); // Panel can not become smaller than MIN_PART_HEIGHT + doLayout = newSashHeight >= this.partLayoutInfo.panel.minHeight; } } // Panel hidden else { - if (startY - e.currentY >= this.computedStyles.panel.minHeight) { + if (startY - e.currentY >= this.partLayoutInfo.panel.minHeight) { this.startPanelHeight = 0; - this.panelHeight = this.computedStyles.panel.minHeight; - this.partService.setPanelHidden(false); + this.panelHeight = this.partLayoutInfo.panel.minHeight; + promise = this.partService.setPanelHidden(false); } } if (doLayout) { - this.layout(); + promise.done(() => this.layout(), errors.onUnexpectedError); } }); @@ -226,17 +256,15 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal this.sashY.addListener2('reset', () => { this.panelHeight = this.sidebarHeight * DEFAULT_PANEL_HEIGHT_COEFFICIENT; this.storageService.store(WorkbenchLayout.sashYHeightSettingsKey, this.panelHeight, StorageScope.GLOBAL); - this.partService.setPanelHidden(false); - this.layout(); + this.partService.setPanelHidden(false).done(() => this.layout(), errors.onUnexpectedError); }); this.sashX.addListener2('reset', () => { let activeViewlet = this.viewletService.getActiveViewlet(); let optimalWidth = activeViewlet && activeViewlet.getOptimalWidth(); - this.sidebarWidth = Math.max(DEFAULT_MIN_SIDEBAR_PART_WIDTH, optimalWidth || 0); + this.sidebarWidth = Math.max(MIN_SIDEBAR_PART_WIDTH, optimalWidth || 0); this.storageService.store(WorkbenchLayout.sashXWidthSettingsKey, this.sidebarWidth, StorageScope.GLOBAL); - this.partService.setSideBarHidden(false); - this.layout(); + this.partService.setSideBarHidden(false).done(() => this.layout(), errors.onUnexpectedError); }); } @@ -248,8 +276,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal if (this.workbenchSize && (this.sidebarWidth || this.panelHeight)) { let visibleEditors = this.editorService.getVisibleEditors().length; if (visibleEditors > 1) { - const sidebarOverflow = this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * DEFAULT_MIN_EDITOR_PART_WIDTH); - const panelOverflow = !this.layoutEditorGroupsVertically && (this.workbenchSize.height - this.panelHeight < visibleEditors * DEFAULT_MIN_EDITOR_PART_HEIGHT); + const sidebarOverflow = this.layoutEditorGroupsVertically && (this.workbenchSize.width - this.sidebarWidth < visibleEditors * MIN_EDITOR_PART_WIDTH); + const panelOverflow = !this.layoutEditorGroupsVertically && (this.workbenchSize.height - this.panelHeight < visibleEditors * MIN_EDITOR_PART_HEIGHT); if (sidebarOverflow || panelOverflow) { this.layout(); @@ -269,68 +297,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } } - private relayout(): void { - - // Recompute Styles - this.computeStyle(); - this.editor.getLayout().computeStyle(); - this.sidebar.getLayout().computeStyle(); - this.panel.getLayout().computeStyle(); - - // Trigger Layout - this.layout(); - } - - private computeStyle(): void { - const titlebarStyle = this.titlebar.getContainer().getComputedStyle(); - const sidebarStyle = this.sidebar.getContainer().getComputedStyle(); - const panelStyle = this.panel.getContainer().getComputedStyle(); - const editorStyle = this.editor.getContainer().getComputedStyle(); - const activitybarStyle = this.activitybar.getContainer().getComputedStyle(); - const statusbarStyle = this.statusbar.getContainer().getComputedStyle(); - - // Determine styles by looking into their CSS - this.computedStyles = { - titlebar: { - height: parseInt(titlebarStyle.getPropertyValue('height'), 10) - }, - activitybar: { - width: parseInt(activitybarStyle.getPropertyValue('width'), 10) - }, - sidebar: { - minWidth: parseInt(sidebarStyle.getPropertyValue('min-width'), 10) || DEFAULT_MIN_SIDEBAR_PART_WIDTH - }, - panel: { - minHeight: parseInt(panelStyle.getPropertyValue('min-height'), 10) || DEFAULT_MIN_PANEL_PART_HEIGHT - }, - editor: { - minWidth: parseInt(editorStyle.getPropertyValue('min-width'), 10) || DEFAULT_MIN_EDITOR_PART_WIDTH, - minHeight: DEFAULT_MIN_EDITOR_PART_HEIGHT - }, - statusbar: { - height: parseInt(statusbarStyle.getPropertyValue('height'), 10) - } - }; - - // Always keep the initial computed styles - if (!this.initialComputedStyles) { - this.initialComputedStyles = this.computedStyles; - } - } - public layout(options?: ILayoutOptions): void { - if (options && options.forceStyleRecompute) { - this.computeStyle(); - this.editor.getLayout().computeStyle(); - this.sidebar.getLayout().computeStyle(); - this.panel.getLayout().computeStyle(); - } - - if (!this.computedStyles) { - this.computeStyle(); - } - - this.workbenchSize = this.getWorkbenchArea(); + this.workbenchSize = this.parent.getClientArea(); const isActivityBarHidden = !this.partService.isVisible(Parts.ACTIVITYBAR_PART); const isTitlebarHidden = !this.partService.isVisible(Parts.TITLEBAR_PART); @@ -344,35 +312,35 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal if (isSidebarHidden) { sidebarWidth = 0; } else if (this.sidebarWidth !== -1) { - sidebarWidth = Math.max(this.computedStyles.sidebar.minWidth, this.sidebarWidth); + sidebarWidth = Math.max(this.partLayoutInfo.sidebar.minWidth, this.sidebarWidth); } else { sidebarWidth = this.workbenchSize.width / 5; this.sidebarWidth = sidebarWidth; } - this.statusbarHeight = isStatusbarHidden ? 0 : this.computedStyles.statusbar.height; - this.titlebarHeight = isTitlebarHidden ? 0 : this.initialComputedStyles.titlebar.height / getZoomFactor(); // adjust for zoom prevention + this.statusbarHeight = isStatusbarHidden ? 0 : this.partLayoutInfo.statusbar.height; + this.titlebarHeight = isTitlebarHidden ? 0 : this.partLayoutInfo.titlebar.height / getZoomFactor(); // adjust for zoom prevention this.sidebarHeight = this.workbenchSize.height - this.statusbarHeight - this.titlebarHeight; let sidebarSize = new Dimension(sidebarWidth, this.sidebarHeight); // Activity Bar - this.activitybarWidth = isActivityBarHidden ? 0 : this.computedStyles.activitybar.width; + this.activitybarWidth = isActivityBarHidden ? 0 : this.partLayoutInfo.activitybar.width; let activityBarSize = new Dimension(this.activitybarWidth, sidebarSize.height); // Panel part let panelHeight: number; - const maxPanelHeight = sidebarSize.height - DEFAULT_MIN_EDITOR_PART_HEIGHT; + const maxPanelHeight = sidebarSize.height - MIN_EDITOR_PART_HEIGHT; if (isPanelHidden) { panelHeight = 0; } else if (this.panelHeight > 0) { - panelHeight = Math.min(maxPanelHeight, Math.max(this.computedStyles.panel.minHeight, this.panelHeight)); + panelHeight = Math.min(maxPanelHeight, Math.max(this.partLayoutInfo.panel.minHeight, this.panelHeight)); } else { panelHeight = sidebarSize.height * DEFAULT_PANEL_HEIGHT_COEFFICIENT; } if (options && options.toggleMaximizedPanel) { const heightToSwap = panelHeight; - panelHeight = panelHeight === maxPanelHeight ? Math.max(this.computedStyles.panel.minHeight, Math.min(this.panelHeightBeforeMaximized, maxPanelHeight)) : maxPanelHeight; + panelHeight = panelHeight === maxPanelHeight ? Math.max(this.partLayoutInfo.panel.minHeight, Math.min(this.panelHeightBeforeMaximized, maxPanelHeight)) : maxPanelHeight; this.panelHeightBeforeMaximized = heightToSwap; } const panelDimension = new Dimension(this.workbenchSize.width - sidebarSize.width - activityBarSize.width, panelHeight); @@ -403,8 +371,8 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal } // Assert Sidebar and Editor Size to not overflow - let editorMinWidth = this.computedStyles.editor.minWidth; - let editorMinHeight = this.computedStyles.editor.minHeight; + let editorMinWidth = this.partLayoutInfo.editor.minWidth; + let editorMinHeight = this.partLayoutInfo.editor.minHeight; let visibleEditorCount = this.editorService.getVisibleEditors().length; if (visibleEditorCount > 1) { if (this.layoutEditorGroupsVertically) { @@ -419,14 +387,14 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal editorSize.width = editorMinWidth; panelDimension.width = editorMinWidth; sidebarSize.width -= diff; - sidebarSize.width = Math.max(DEFAULT_MIN_SIDEBAR_PART_WIDTH, sidebarSize.width); + sidebarSize.width = Math.max(MIN_SIDEBAR_PART_WIDTH, sidebarSize.width); } if (editorSize.height < editorMinHeight) { let diff = editorMinHeight - editorSize.height; editorSize.height = editorMinHeight; panelDimension.height -= diff; - panelDimension.height = Math.max(DEFAULT_MIN_PANEL_PART_HEIGHT, panelDimension.height); + panelDimension.height = Math.max(MIN_PANEL_PART_HEIGHT, panelDimension.height); } if (!isSidebarHidden) { @@ -526,15 +494,6 @@ export class WorkbenchLayout implements IVerticalSashLayoutProvider, IHorizontal this.contextViewService.layout(); } - private getWorkbenchArea(): Dimension { - - // Client Area: Parent - let clientArea = this.parent.getClientArea(); - - // Workbench: Client Area - Margins - return clientArea; - } - public getVerticalSashTop(sash: Sash): number { return this.titlebarHeight; } diff --git a/src/vs/workbench/browser/panel.ts b/src/vs/workbench/browser/panel.ts index 862686efabe..7db1606d256 100644 --- a/src/vs/workbench/browser/panel.ts +++ b/src/vs/workbench/browser/panel.ts @@ -84,8 +84,7 @@ export abstract class TogglePanelAction extends Action { public run(): TPromise { if (this.isPanelShowing()) { - this.partService.setPanelHidden(true); - return TPromise.as(true); + return this.partService.setPanelHidden(true); } return this.panelService.openPanel(this.panelId, true); diff --git a/src/vs/workbench/browser/part.ts b/src/vs/workbench/browser/part.ts index 417066997c5..89622e3fa6e 100644 --- a/src/vs/workbench/browser/part.ts +++ b/src/vs/workbench/browser/part.ts @@ -9,18 +9,21 @@ import 'vs/css!./media/part'; import { Dimension, Builder } from 'vs/base/browser/builder'; import { WorkbenchComponent } from 'vs/workbench/common/component'; +export interface IPartOptions { + hasTitle?: boolean; +} + /** - * Parts are layed out in the workbench and have their own layout that arranges a title, - * content and status area to show content. + * Parts are layed out in the workbench and have their own layout that arranges an optional title + * and mandatory content area to show content. */ export abstract class Part extends WorkbenchComponent { private parent: Builder; private titleArea: Builder; private contentArea: Builder; - private statusArea: Builder; private partLayout: PartLayout; - constructor(id: string) { + constructor(id: string, private options: IPartOptions) { super(id); } @@ -28,15 +31,14 @@ export abstract class Part extends WorkbenchComponent { * Note: Clients should not call this method, the workbench calls this * method. Calling it otherwise may result in unexpected behavior. * - * Called to create title, content and status area of the part. + * Called to create title and content area of the part. */ public create(parent: Builder): void { this.parent = parent; this.titleArea = this.createTitleArea(parent); this.contentArea = this.createContentArea(parent); - this.statusArea = this.createStatusArea(parent); - this.partLayout = new PartLayout(this.parent, this.titleArea, this.contentArea, this.statusArea); + this.partLayout = new PartLayout(this.parent, this.options, this.titleArea, this.contentArea); } /** @@ -68,14 +70,7 @@ export abstract class Part extends WorkbenchComponent { } /** - * Subclasses override to provide a status area implementation. - */ - protected createStatusArea(parent: Builder): Builder { - return null; - } - - /** - * Layout title, content and status area in the given dimension. + * Layout title and content area in the given dimension. */ public layout(dimension: Dimension): Dimension[] { return this.partLayout.layout(dimension); @@ -89,100 +84,32 @@ export abstract class Part extends WorkbenchComponent { } } -export class EmptyPart extends Part { - constructor(id: string) { - super(id); - } -} - -interface IContainerStyle { - borderLeftWidth: number; - borderRightWidth: number; - borderTopWidth: number; - borderBottomWidth: number; -} - -interface ITitleStatusStyle { - display: string; - height: number; -} +const TITLE_HEIGHT = 35; export class PartLayout { - private container: Builder; - private titleArea: Builder; - private contentArea: Builder; - private statusArea: Builder; - private titleStyle: ITitleStatusStyle; - private containerStyle: IContainerStyle; - private statusStyle: ITitleStatusStyle; - constructor(container: Builder, titleArea: Builder, contentArea: Builder, statusArea: Builder) { - this.container = container; - this.titleArea = titleArea; - this.contentArea = contentArea; - this.statusArea = statusArea; - } - - public computeStyle(): void { - const containerStyle = this.container.getComputedStyle(); - this.containerStyle = { - borderLeftWidth: parseInt(containerStyle.getPropertyValue('border-left-width'), 10), - borderRightWidth: parseInt(containerStyle.getPropertyValue('border-right-width'), 10), - borderTopWidth: parseInt(containerStyle.getPropertyValue('border-top-width'), 10), - borderBottomWidth: parseInt(containerStyle.getPropertyValue('border-bottom-width'), 10) - }; - - if (this.titleArea) { - const titleStyle = this.titleArea.getComputedStyle(); - this.titleStyle = { - display: titleStyle.getPropertyValue('display'), - height: this.titleArea.getTotalSize().height - }; - } - - if (this.statusArea) { - const statusStyle = this.statusArea.getComputedStyle(); - this.statusStyle = { - display: statusStyle.getPropertyValue('display'), - height: this.statusArea.getTotalSize().height - }; - } + constructor(private container: Builder, private options: IPartOptions, private titleArea: Builder, private contentArea: Builder) { } public layout(dimension: Dimension): Dimension[] { - if (!this.containerStyle) { - this.computeStyle(); - } + const {width, height} = dimension; - const width = dimension.width - (this.containerStyle.borderLeftWidth + this.containerStyle.borderRightWidth); - const height = dimension.height - (this.containerStyle.borderTopWidth + this.containerStyle.borderBottomWidth); - - // Return the applied sizes to title, content and status + // Return the applied sizes to title and content const sizes: Dimension[] = []; // Title Size: Width (Fill), Height (Variable) let titleSize: Dimension; - if (this.titleArea && this.titleStyle.display !== 'none') { - titleSize = new Dimension(width, Math.min(height, this.titleStyle.height)); + if (this.options && this.options.hasTitle) { + titleSize = new Dimension(width, Math.min(height, TITLE_HEIGHT)); } else { titleSize = new Dimension(0, 0); } - // Status Size: Width (Fill), Height (Variable) - let statusSize: Dimension; - if (this.statusArea && this.statusStyle.display !== 'none') { - this.statusArea.getHTMLElement().style.height = this.statusArea.getHTMLElement().style.width = ''; - statusSize = new Dimension(width, Math.min(height - titleSize.height, this.statusStyle.height)); - } else { - statusSize = new Dimension(0, 0); - } - // Content Size: Width (Fill), Height (Variable) - const contentSize = new Dimension(width, height - titleSize.height - statusSize.height); + const contentSize = new Dimension(width, height - titleSize.height); sizes.push(titleSize); sizes.push(contentSize); - sizes.push(statusSize); // Content if (this.contentArea) { diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts index 7cee0fd511a..ba253f8beee 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarActions.ts @@ -8,7 +8,6 @@ import 'vs/css!./media/activityaction'; import nls = require('vs/nls'); import DOM = require('vs/base/browser/dom'); -import errors = require('vs/base/common/errors'); import { TPromise } from 'vs/base/common/winjs.base'; import { Builder, $ } from 'vs/base/browser/builder'; import { DelayedDragHandler } from 'vs/base/browser/dnd'; @@ -93,13 +92,11 @@ export class ViewletActivityAction extends ActivityAction { // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) { - this.partService.setSideBarHidden(true); - } else { - this.viewletService.openViewlet(this.viewlet.id, true).done(null, errors.onUnexpectedError); - this.activate(); + return this.partService.setSideBarHidden(true); } - return TPromise.as(true); + return this.viewletService.openViewlet(this.viewlet.id, true) + .then(() => this.activate()); } } @@ -531,12 +528,10 @@ class OpenViewletAction extends Action { // Hide sidebar if selected viewlet already visible if (sideBarVisible && activeViewlet && activeViewlet.getId() === this.viewlet.id) { - this.partService.setSideBarHidden(true); - } else { - this.viewletService.openViewlet(this.viewlet.id, true).done(null, errors.onUnexpectedError); + return this.partService.setSideBarHidden(true); } - return TPromise.as(true); + return this.viewletService.openViewlet(this.viewlet.id, true); } } diff --git a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts index fe855b35e7f..0129517d71c 100644 --- a/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts +++ b/src/vs/workbench/browser/parts/activitybar/activitybarPart.ts @@ -64,7 +64,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { @IInstantiationService private instantiationService: IInstantiationService, @IPartService private partService: IPartService ) { - super(id); + super(id, { hasTitle: false }); this.viewletIdToActionItems = Object.create(null); this.viewletIdToActions = Object.create(null); @@ -350,7 +350,7 @@ export class ActivitybarPart extends Part implements IActivityBarService { // Case: we closed the last visible viewlet // Solv: we hide the sidebar else if (visibleViewlets.length === 1) { - unpinPromise = TPromise.as(this.partService.setSideBarHidden(true)); + unpinPromise = this.partService.setSideBarHidden(true); } // Case: we closed the default viewlet diff --git a/src/vs/workbench/browser/parts/compositePart.ts b/src/vs/workbench/browser/parts/compositePart.ts index 044e06f98c2..f8e3f8211af 100644 --- a/src/vs/workbench/browser/parts/compositePart.ts +++ b/src/vs/workbench/browser/parts/compositePart.ts @@ -20,7 +20,7 @@ import { IActionItem, ActionsOrientation } from 'vs/base/browser/ui/actionbar/ac import { ProgressBar } from 'vs/base/browser/ui/progressbar/progressbar'; import { IActionBarRegistry, Extensions, prepareActions } from 'vs/workbench/browser/actionBarRegistry'; import { Action, IAction } from 'vs/base/common/actions'; -import { Part } from 'vs/workbench/browser/part'; +import { Part, IPartOptions } from 'vs/workbench/browser/part'; import { Composite, CompositeRegistry } from 'vs/workbench/browser/composite'; import { IComposite } from 'vs/workbench/common/composite'; import { WorkbenchProgressService } from 'vs/workbench/services/progress/browser/progressService'; @@ -65,9 +65,10 @@ export abstract class CompositePart extends Part { private nameForTelemetry: string, private compositeCSSClass: string, private actionContributionScope: string, - id: string + id: string, + options: IPartOptions ) { - super(id); + super(id, options); this.instantiatedCompositeListeners = []; this.mapCompositeToCompositeContainer = {}; diff --git a/src/vs/workbench/browser/parts/editor/baseEditor.ts b/src/vs/workbench/browser/parts/editor/baseEditor.ts index 1b584afd8df..35fd51b7004 100644 --- a/src/vs/workbench/browser/parts/editor/baseEditor.ts +++ b/src/vs/workbench/browser/parts/editor/baseEditor.ts @@ -83,7 +83,7 @@ export abstract class BaseEditor extends Panel implements IEditor { /** * Called to create the editor in the parent builder. */ - public abstract createEditor(parent: Builder): void; + protected abstract createEditor(parent: Builder): void; /** * Overload this function to allow for passing in a position argument. @@ -99,7 +99,7 @@ export abstract class BaseEditor extends Panel implements IEditor { return promise; } - public setEditorVisible(visible: boolean, position: Position = null): void { + protected setEditorVisible(visible: boolean, position: Position = null): void { this._position = position; } diff --git a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts index fef79c08e73..2a257d446f2 100644 --- a/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryDiffEditor.ts @@ -59,7 +59,7 @@ export class BinaryResourceDiffEditor extends BaseEditor implements IVerticalSas return this.input ? this.input.getName() : nls.localize('binaryDiffEditor', "Binary Diff Viewer"); } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { // Left Container for Binary const leftBinaryContainerElement = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/editor/binaryEditor.ts b/src/vs/workbench/browser/parts/editor/binaryEditor.ts index e28966bcdeb..2d319de8156 100644 --- a/src/vs/workbench/browser/parts/editor/binaryEditor.ts +++ b/src/vs/workbench/browser/parts/editor/binaryEditor.ts @@ -44,7 +44,7 @@ export abstract class BaseBinaryResourceEditor extends BaseEditor { return this.input ? this.input.getName() : nls.localize('binaryEditor', "Binary Viewer"); } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { // Container for Binary const binaryContainerElement = document.createElement('div'); diff --git a/src/vs/workbench/browser/parts/editor/editorActions.ts b/src/vs/workbench/browser/parts/editor/editorActions.ts index e70751ab74f..7086cc67ca2 100644 --- a/src/vs/workbench/browser/parts/editor/editorActions.ts +++ b/src/vs/workbench/browser/parts/editor/editorActions.ts @@ -824,7 +824,7 @@ export class MaximizeGroupAction extends Action { public run(): TPromise { if (this.editorService.getActiveEditor()) { this.editorGroupService.arrangeGroups(GroupArrangement.MINIMIZE_OTHERS); - this.partService.setSideBarHidden(true); + return this.partService.setSideBarHidden(true); } return TPromise.as(false); diff --git a/src/vs/workbench/browser/parts/editor/editorPart.ts b/src/vs/workbench/browser/parts/editor/editorPart.ts index 162ea2b23e7..dae95332fd1 100644 --- a/src/vs/workbench/browser/parts/editor/editorPart.ts +++ b/src/vs/workbench/browser/parts/editor/editorPart.ts @@ -115,7 +115,7 @@ export class EditorPart extends Part implements IEditorPart, IEditorGroupService @IContextKeyService contextKeyService: IContextKeyService, @IInstantiationService private instantiationService: IInstantiationService ) { - super(id); + super(id, { hasTitle: false }); this._onEditorsChanged = new Emitter(); this._onEditorsMoved = new Emitter(); diff --git a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts index 45f8f19f20b..63b5dd54eed 100644 --- a/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts +++ b/src/vs/workbench/browser/parts/editor/sideBySideEditor.ts @@ -39,7 +39,7 @@ export class SideBySideEditor extends BaseEditor { super(SideBySideEditor.ID, telemetryService); } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { const parentElement = parent.getHTMLElement(); DOM.addClass(parentElement, 'side-by-side-editor'); this.createSash(parentElement); @@ -51,7 +51,7 @@ export class SideBySideEditor extends BaseEditor { .then(() => this.updateInput(oldInput, newInput, options)); } - public setEditorVisible(visible: boolean, position: Position): void { + protected setEditorVisible(visible: boolean, position: Position): void { if (this.masterEditor) { this.masterEditor.setVisible(visible, position); } diff --git a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts index 621985ff420..9fc75180eca 100644 --- a/src/vs/workbench/browser/parts/editor/textDiffEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textDiffEditor.ts @@ -8,13 +8,14 @@ import 'vs/css!./media/textdiffeditor'; import { TPromise } from 'vs/base/common/winjs.base'; import nls = require('vs/nls'); +import objects = require('vs/base/common/objects'); import { Builder } from 'vs/base/browser/builder'; import { Action, IAction } from 'vs/base/common/actions'; import { onUnexpectedError } from 'vs/base/common/errors'; import types = require('vs/base/common/types'); import { IDiffEditor } from 'vs/editor/browser/editorBrowser'; import { IDiffEditorOptions, IEditorOptions } from 'vs/editor/common/editorCommon'; -import { BaseTextEditor } from 'vs/workbench/browser/parts/editor/textEditor'; +import { BaseTextEditor, IEditorConfiguration } from 'vs/workbench/browser/parts/editor/textEditor'; import { TextEditorOptions, TextDiffEditorOptions, EditorModel, EditorInput, EditorOptions, TEXT_DIFF_EDITOR_ID } from 'vs/workbench/common/editor'; import { StringEditorInput } from 'vs/workbench/common/editor/stringEditorInput'; import { ResourceEditorInput } from 'vs/workbench/common/editor/resourceEditorInput'; @@ -66,7 +67,7 @@ export class TextDiffEditor extends BaseTextEditor { return nls.localize('textDiffEditor', "Text Diff Editor"); } - public createEditorControl(parent: Builder): IDiffEditor { + public createEditorControl(parent: Builder, configuration: IEditorOptions): IDiffEditor { // Actions this.nextDiffAction = new NavigateAction(this, true); @@ -103,7 +104,7 @@ export class TextDiffEditor extends BaseTextEditor { // Create a special child of instantiator that will delegate all calls to openEditor() to the same diff editor if the input matches with the modified one const diffEditorInstantiator = this.instantiationService.createChild(new ServiceCollection([IWorkbenchEditorService, delegatingEditorService])); - return diffEditorInstantiator.createInstance(DiffEditorWidget, parent.getHTMLElement(), this.getCodeEditorOptions()); + return diffEditorInstantiator.createInstance(DiffEditorWidget, parent.getHTMLElement(), configuration); } public setInput(input: EditorInput, options?: EditorOptions): TPromise { @@ -167,9 +168,6 @@ export class TextDiffEditor extends BaseTextEditor { if (options && types.isFunction((options).apply)) { (options).apply(diffEditor); } - - // Apply options again because input has changed - diffEditor.updateOptions(this.getCodeEditorOptions()); }, (error) => { // In case we tried to open a file and the response indicates that this is not a text file, fallback to binary diff. @@ -197,8 +195,19 @@ export class TextDiffEditor extends BaseTextEditor { return false; } - protected getCodeEditorOptions(): IEditorOptions { - const options: IDiffEditorOptions = super.getCodeEditorOptions(); + protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions { + const editorConfiguration = super.computeConfiguration(configuration); + + // Handle diff editor specially by merging in diffEditor configuration + if (types.isObject(configuration.diffEditor)) { + objects.mixin(editorConfiguration, configuration.diffEditor); + } + + return editorConfiguration; + } + + protected getConfigurationOverrides(): IEditorOptions { + const options: IDiffEditorOptions = super.getConfigurationOverrides(); const input = this.input; if (input instanceof DiffEditorInput) { diff --git a/src/vs/workbench/browser/parts/editor/textEditor.ts b/src/vs/workbench/browser/parts/editor/textEditor.ts index 18a16159854..090500a91c1 100644 --- a/src/vs/workbench/browser/parts/editor/textEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textEditor.ts @@ -14,7 +14,7 @@ import DOM = require('vs/base/browser/dom'); import { CodeEditor } from 'vs/editor/browser/codeEditor'; import { EditorInput, EditorOptions } from 'vs/workbench/common/editor'; import { BaseEditor } from 'vs/workbench/browser/parts/editor/baseEditor'; -import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType, EditorType } from 'vs/editor/common/editorCommon'; +import { IEditorViewState, IEditor, IEditorOptions, EventType as EditorEventType } from 'vs/editor/common/editorCommon'; import { Position } from 'vs/platform/editor/common/editor'; import { IStorageService } from 'vs/platform/storage/common/storage'; import { IConfigurationService } from 'vs/platform/configuration/common/configuration'; @@ -33,7 +33,7 @@ interface ITextEditorViewState { 2?: IEditorViewState; } -interface IEditorConfiguration { +export interface IEditorConfiguration { editor: any; diffEditor: any; } @@ -87,20 +87,22 @@ export abstract class BaseTextEditor extends BaseEditor { return; } - // Specific editor options always overwrite user configuration - const editorConfiguration = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null); - objects.assign(editorConfiguration, this.getCodeEditorOptions()); - - // Handle diff editor specially by merging in diffEditor configuration - if (this.editorControl.getEditorType() === EditorType.IDiffEditor && types.isObject(configuration.diffEditor)) { - objects.mixin(editorConfiguration, configuration.diffEditor); - } + const editorConfiguration = this.computeConfiguration(configuration); // Apply to control this.editorControl.updateOptions(editorConfiguration); } - protected getCodeEditorOptions(): IEditorOptions { + protected computeConfiguration(configuration: IEditorConfiguration): IEditorOptions { + + // Specific editor options always overwrite user configuration + const editorConfiguration = types.isObject(configuration.editor) ? objects.clone(configuration.editor) : Object.create(null); + objects.assign(editorConfiguration, this.getConfigurationOverrides()); + + return editorConfiguration; + } + + protected getConfigurationOverrides(): IEditorOptions { return { overviewRulerLanes: 3, lineNumbersMinChars: 3, @@ -109,20 +111,29 @@ export abstract class BaseTextEditor extends BaseEditor { }; } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { // Editor for Text this._editorContainer = parent; - this.editorControl = this.createEditorControl(parent); + this.editorControl = this.createEditorControl(parent, this.computeConfiguration(this.configurationService.getConfiguration())); // Application & Editor focus change if (this.editorControl instanceof EventEmitter) { this.toUnbind.push(this.editorControl.addListener2(EditorEventType.EditorBlur, () => this.onEditorFocusLost())); } this.toUnbind.push(DOM.addDisposableListener(window, DOM.EventType.BLUR, () => this.onWindowFocusLost())); + } - // Configuration - this.applyConfiguration(this.configurationService.getConfiguration()); + /** + * This method creates and returns the text editor control to be used. Subclasses can override to + * provide their own editor control that should be used (e.g. a DiffEditor). + * + * The passed in configuration object should be passed to the editor control when creating it. + */ + protected createEditorControl(parent: Builder, configuration: IEditorOptions): IEditor { + + // Use a getter for the instantiation service since some subclasses might use scoped instantiation services + return this.instantiationService.createInstance(CodeEditor, parent.getHTMLElement(), configuration); } private onEditorFocusLost(): void { @@ -158,23 +169,16 @@ export abstract class BaseTextEditor extends BaseEditor { }); } - /** - * This method creates and returns the text editor control to be used. Subclasses can override to - * provide their own editor control that should be used (e.g. a DiffEditor). - */ - public createEditorControl(parent: Builder): IEditor { - - // Use a getter for the instantiation service since some subclasses might use scoped instantiation services - return this.instantiationService.createInstance(CodeEditor, parent.getHTMLElement(), this.getCodeEditorOptions()); - } - public setInput(input: EditorInput, options?: EditorOptions): TPromise { return super.setInput(input, options).then(() => { - this.editorControl.updateOptions(this.getCodeEditorOptions()); // support input specific editor options + + // Update editor options after having set the input. We do this because there can be + // editor input specific options (e.g. an ARIA label depending on the input showing) + this.editorControl.updateOptions(this.getConfigurationOverrides()); }); } - public setEditorVisible(visible: boolean, position: Position = null): void { + protected setEditorVisible(visible: boolean, position: Position = null): void { // Pass on to Editor if (visible) { diff --git a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts index 88871b501c4..25299ae5adb 100644 --- a/src/vs/workbench/browser/parts/editor/textResourceEditor.ts +++ b/src/vs/workbench/browser/parts/editor/textResourceEditor.ts @@ -111,9 +111,6 @@ export class TextResourceEditor extends BaseTextEditor { if (!optionsGotApplied) { this.restoreViewState(input); } - - // Apply options again because input has changed - textEditor.updateOptions(this.getCodeEditorOptions()); }); } @@ -126,8 +123,8 @@ export class TextResourceEditor extends BaseTextEditor { } } - protected getCodeEditorOptions(): IEditorOptions { - const options = super.getCodeEditorOptions(); + protected getConfigurationOverrides(): IEditorOptions { + const options = super.getConfigurationOverrides(); const input = this.input; const isUntitled = input instanceof UntitledEditorInput; diff --git a/src/vs/workbench/browser/parts/panel/panelPart.ts b/src/vs/workbench/browser/parts/panel/panelPart.ts index 5d0395fd3f5..f3e7b5973f8 100644 --- a/src/vs/workbench/browser/parts/panel/panelPart.ts +++ b/src/vs/workbench/browser/parts/panel/panelPart.ts @@ -58,7 +58,8 @@ export class PanelPart extends CompositePart implements IPanelService { 'panel', 'panel', Scope.PANEL, - id + id, + { hasTitle: true } ); } @@ -80,16 +81,17 @@ export class PanelPart extends CompositePart implements IPanelService { } // First check if panel is hidden and show if so + let promise = TPromise.as(null); if (!this.partService.isVisible(Parts.PANEL_PART)) { try { this.blockOpeningPanel = true; - this.partService.setPanelHidden(false); + promise = this.partService.setPanelHidden(false); } finally { this.blockOpeningPanel = false; } } - return this.openComposite(id, focus); + return promise.then(() => this.openComposite(id, focus)); } protected getActions(): IAction[] { @@ -122,9 +124,8 @@ class ClosePanelAction extends Action { super(id, name, 'hide-panel-action'); } - public run(): TPromise { - this.partService.setPanelHidden(true); - return TPromise.as(true); + public run(): TPromise { + return this.partService.setPanelHidden(true); } } @@ -140,9 +141,8 @@ export class TogglePanelAction extends ActivityAction { super(id, name, partService.isVisible(Parts.PANEL_PART) ? 'panel expanded' : 'panel'); } - public run(): TPromise { - this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART)); - return TPromise.as(true); + public run(): TPromise { + return this.partService.setPanelHidden(this.partService.isVisible(Parts.PANEL_PART)); } } @@ -160,21 +160,18 @@ class FocusPanelAction extends Action { super(id, label); } - public run(): TPromise { + public run(): TPromise { // Show panel if (!this.partService.isVisible(Parts.PANEL_PART)) { - this.partService.setPanelHidden(false); + return this.partService.setPanelHidden(false); } // Focus into active panel - else { - let panel = this.panelService.getActivePanel(); - if (panel) { - panel.focus(); - } + let panel = this.panelService.getActivePanel(); + if (panel) { + panel.focus(); } - return TPromise.as(true); } } @@ -192,12 +189,10 @@ class ToggleMaximizedPanelAction extends Action { super(id, label); } - public run(): TPromise { + public run(): TPromise { // Show panel - this.partService.setPanelHidden(false); - this.partService.toggleMaximizedPanel(); - - return TPromise.as(true); + return this.partService.setPanelHidden(false) + .then(() => this.partService.toggleMaximizedPanel()); } } diff --git a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts index c381a4b44c2..cdbf0867f75 100644 --- a/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts +++ b/src/vs/workbench/browser/parts/sidebar/sidebarPart.ts @@ -65,7 +65,8 @@ export class SidebarPart extends CompositePart implements ISidebar { 'sideBar', 'viewlet', Scope.VIEWLET, - id + id, + { hasTitle: true } ); } @@ -83,16 +84,17 @@ export class SidebarPart extends CompositePart implements ISidebar { } // First check if sidebar is hidden and show if so + let promise = TPromise.as(null); if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { try { this.blockOpeningViewlet = true; - this.partService.setSideBarHidden(false); + promise = this.partService.setSideBarHidden(false); } finally { this.blockOpeningViewlet = false; } } - return this.openComposite(id, focus); + return promise.then(() => this.openComposite(id, focus)); } public getActiveViewlet(): IViewlet { @@ -122,21 +124,18 @@ class FocusSideBarAction extends Action { super(id, label); } - public run(): TPromise { + public run(): TPromise { // Show side bar if (!this.partService.isVisible(Parts.SIDEBAR_PART)) { - this.partService.setSideBarHidden(false); + return this.partService.setSideBarHidden(false); } // Focus into active viewlet - else { - let viewlet = this.viewletService.getActiveViewlet(); - if (viewlet) { - viewlet.focus(); - } + let viewlet = this.viewletService.getActiveViewlet(); + if (viewlet) { + viewlet.focus(); } - return TPromise.as(true); } } diff --git a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts index ac76450fd17..54ad8b61c3b 100644 --- a/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts +++ b/src/vs/workbench/browser/parts/statusbar/statusbarPart.ts @@ -42,7 +42,7 @@ export class StatusbarPart extends Part implements IStatusbarService { id: string, @IInstantiationService private instantiationService: IInstantiationService ) { - super(id); + super(id, { hasTitle: false }); this.toDispose = []; } diff --git a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts index f758d84fa1b..ac2659b74a9 100644 --- a/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts +++ b/src/vs/workbench/browser/parts/titlebar/titlebarPart.ts @@ -35,7 +35,7 @@ export class TitlebarPart extends Part implements ITitleService { @IWindowService private windowService: IWindowService, @IWindowsService private windowsService: IWindowsService ) { - super(id); + super(id, { hasTitle: false }); this.registerListeners(); } diff --git a/src/vs/workbench/electron-browser/workbench.ts b/src/vs/workbench/electron-browser/workbench.ts index ff5e2429ae5..f272385d250 100644 --- a/src/vs/workbench/electron-browser/workbench.ts +++ b/src/vs/workbench/electron-browser/workbench.ts @@ -662,7 +662,7 @@ export class Workbench implements IPartService { // Layout if (!skipLayout) { - this.workbenchLayout.layout({ forceStyleRecompute: true }); + this.workbenchLayout.layout(); } } @@ -672,11 +672,11 @@ export class Workbench implements IPartService { // Layout if (!skipLayout) { - this.workbenchLayout.layout({ forceStyleRecompute: true }); + this.workbenchLayout.layout(); } } - public setSideBarHidden(hidden: boolean, skipLayout?: boolean): void { + public setSideBarHidden(hidden: boolean, skipLayout?: boolean): TPromise { this.sideBarHidden = hidden; // Adjust CSS @@ -686,39 +686,42 @@ export class Workbench implements IPartService { this.workbench.removeClass('nosidebar'); } - // Layout - if (!skipLayout) { - this.workbenchLayout.layout({ forceStyleRecompute: true }); - } - + let promise = TPromise.as(null); // If sidebar becomes hidden, also hide the current active Viewlet if any if (hidden && this.sidebarPart.getActiveViewlet()) { - this.sidebarPart.hideActiveViewlet(); + promise = this.sidebarPart.hideActiveViewlet().then(() => { + const activeEditor = this.editorPart.getActiveEditor(); + const activePanel = this.panelPart.getActivePanel(); - const activeEditor = this.editorPart.getActiveEditor(); - const activePanel = this.panelPart.getActivePanel(); - - // Pass Focus to Editor or Panel if Sidebar is now hidden - if (this.hasFocus(Parts.PANEL_PART) && activePanel) { - activePanel.focus(); - } else if (activeEditor) { - activeEditor.focus(); - } + // Pass Focus to Editor or Panel if Sidebar is now hidden + if (this.hasFocus(Parts.PANEL_PART) && activePanel) { + activePanel.focus(); + } else if (activeEditor) { + activeEditor.focus(); + } + }); } // If sidebar becomes visible, show last active Viewlet or default viewlet else if (!hidden && !this.sidebarPart.getActiveViewlet()) { const viewletToOpen = this.sidebarPart.getLastActiveViewletId() || this.viewletService.getDefaultViewletId(); if (viewletToOpen) { - this.sidebarPart.openViewlet(viewletToOpen, true).done(null, errors.onUnexpectedError); + promise = this.sidebarPart.openViewlet(viewletToOpen, true); } } - // Remember in settings - this.storageService.store(Workbench.sidebarHiddenSettingKey, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + return promise.then(() => { + // Remember in settings + this.storageService.store(Workbench.sidebarHiddenSettingKey, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + + // Layout + if (!skipLayout) { + this.workbenchLayout.layout(); + } + }); } - public setPanelHidden(hidden: boolean, skipLayout?: boolean): void { + public setPanelHidden(hidden: boolean, skipLayout?: boolean): TPromise { this.panelHidden = hidden; // Adjust CSS @@ -728,20 +731,16 @@ export class Workbench implements IPartService { this.workbench.removeClass('nopanel'); } - // Layout - if (!skipLayout) { - this.workbenchLayout.layout({ forceStyleRecompute: true }); - } - + let promise = TPromise.as(null); // If panel part becomes hidden, also hide the current active panel if any if (hidden && this.panelPart.getActivePanel()) { - this.panelPart.hideActivePanel(); - - // Pass Focus to Editor if Panel part is now hidden - const editor = this.editorPart.getActiveEditor(); - if (editor) { - editor.focus(); - } + promise = this.panelPart.hideActivePanel().then(() => { + // Pass Focus to Editor if Panel part is now hidden + const editor = this.editorPart.getActiveEditor(); + if (editor) { + editor.focus(); + } + }); } // If panel part becomes visible, show last active panel or default panel @@ -749,16 +748,23 @@ export class Workbench implements IPartService { const registry = Registry.as(PanelExtensions.Panels); const panelToOpen = this.panelPart.getLastActivePanelId() || registry.getDefaultPanelId(); if (panelToOpen) { - this.panelPart.openPanel(panelToOpen, true).done(null, errors.onUnexpectedError); + promise = this.panelPart.openPanel(panelToOpen, true); } } - // Remember in settings - this.storageService.store(Workbench.panelHiddenSettingKey, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + return promise.then(() => { + // Remember in settings + this.storageService.store(Workbench.panelHiddenSettingKey, hidden ? 'true' : 'false', StorageScope.WORKSPACE); + + // Layout + if (!skipLayout) { + this.workbenchLayout.layout(); + } + }); } public toggleMaximizedPanel(): void { - this.workbenchLayout.layout({ forceStyleRecompute: true, toggleMaximizedPanel: true }); + this.workbenchLayout.layout({ toggleMaximizedPanel: true }); } public getSideBarPosition(): Position { @@ -767,7 +773,7 @@ export class Workbench implements IPartService { private setSideBarPosition(position: Position): void { if (this.sideBarHidden) { - this.setSideBarHidden(false, true /* Skip Layout */); + this.setSideBarHidden(false, true /* Skip Layout */).done(undefined, errors.onUnexpectedError); } const newPositionValue = (position === Position.LEFT) ? 'left' : 'right'; @@ -781,7 +787,7 @@ export class Workbench implements IPartService { this.sidebarPart.getContainer().addClass(newPositionValue); // Layout - this.workbenchLayout.layout({ forceStyleRecompute: true }); + this.workbenchLayout.layout(); } public dispose(): void { @@ -1072,8 +1078,8 @@ export class Workbench implements IPartService { this.zenMode.transitionedToFullScreen = toggleFullScreen; this.zenMode.wasSideBarVisible = this.isVisible(Parts.SIDEBAR_PART); this.zenMode.wasPanelVisible = this.isVisible(Parts.PANEL_PART); - this.setPanelHidden(true, true); - this.setSideBarHidden(true, true); + this.setPanelHidden(true, true).done(undefined, errors.onUnexpectedError); + this.setSideBarHidden(true, true).done(undefined, errors.onUnexpectedError); this.setActivityBarHidden(true, true); if (config.hideStatusBar) { @@ -1084,10 +1090,10 @@ export class Workbench implements IPartService { } } else { if (this.zenMode.wasPanelVisible) { - this.setPanelHidden(false, true); + this.setPanelHidden(false, true).done(undefined, errors.onUnexpectedError); } if (this.zenMode.wasSideBarVisible) { - this.setSideBarHidden(false, true); + this.setSideBarHidden(false, true).done(undefined, errors.onUnexpectedError); } // Status bar and activity bar visibility come from settings -> update their visibility. this.onDidUpdateConfiguration(true); diff --git a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts index e58380990f8..539ed2b4206 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugConfigurationManager.ts @@ -333,7 +333,7 @@ export class ConfigurationManager implements debug.IConfigurationManager { let configFileCreated = false; return this.fileService.resolveContent(resource).then(content => true, err => - this.quickOpenService.pick([...this.adapters, { label: 'More...' }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) + this.quickOpenService.pick([...this.adapters.filter(a => a.hasInitialConfiguration()), { label: 'More...' }], { placeHolder: nls.localize('selectDebug', "Select Environment") }) .then(picked => { if (picked instanceof Adapter) { return picked ? picked.getInitialConfigurationContent() : null; diff --git a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts index 4fd7b01fa9d..074af2646d1 100644 --- a/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts +++ b/src/vs/workbench/parts/debug/electron-browser/debugEditorContribution.ts @@ -274,13 +274,13 @@ export class DebugEditorContribution implements IDebugEditorContribution { if (model && LAUNCH_JSON_REGEX.test(model.uri.toString())) { this.configurationWidget = this.instantiationService.createInstance(FloatingClickWidget, this.editor, nls.localize('addConfiguration', "Add Configuration"), null); this.configurationWidget.render(); - this.toDispose.push(this.configurationWidget.onClick(() => this.addLaunchConfiguration())); + this.toDispose.push(this.configurationWidget.onClick(() => this.addLaunchConfiguration().done(undefined, errors.onUnexpectedError))); } else if (this.configurationWidget) { this.configurationWidget.dispose(); } } - private addLaunchConfiguration(): void { + public addLaunchConfiguration(): TPromise { let configurationsPosition: IPosition; const model = this.editor.getModel(); let depthInArray = 0; @@ -315,7 +315,7 @@ export class DebugEditorContribution implements IDebugEditorContribution { return this.commandService.executeCommand('editor.action.insertLineAfter'); }; - insertLineAfter(configurationsPosition.lineNumber).done(() => this.commandService.executeCommand('editor.action.triggerSuggest'), errors.onUnexpectedError); + return insertLineAfter(configurationsPosition.lineNumber).then(() => this.commandService.executeCommand('editor.action.triggerSuggest')); } private static BREAKPOINT_HELPER_DECORATION: IModelDecorationOptions = { diff --git a/src/vs/workbench/parts/debug/node/debugAdapter.ts b/src/vs/workbench/parts/debug/node/debugAdapter.ts index 166d7612deb..13bef462821 100644 --- a/src/vs/workbench/parts/debug/node/debugAdapter.ts +++ b/src/vs/workbench/parts/debug/node/debugAdapter.ts @@ -84,6 +84,10 @@ export class Adapter { objects.mixin(this.rawAdapter, secondRawAdapter, extensionDescription.isBuiltin); } + public hasInitialConfiguration(): boolean { + return !!this.rawAdapter.initialConfigurations; + } + public getInitialConfigurationContent(): TPromise { const editorConfig = this.configurationService.getConfiguration(); if (typeof this.rawAdapter.initialConfigurations === 'string') { diff --git a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts index 64e21cd4d8d..7dc3eb0d8a6 100644 --- a/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts +++ b/src/vs/workbench/parts/extensions/node/extensionsWorkbenchService.ts @@ -500,7 +500,7 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { return TPromise.wrap(null); } - return this.doSetEnablement(extension, enable, workspace).then(reload => { + return this.promptAndSetEnablement(extension, enable, workspace).then(reload => { this.telemetryService.publicLog(enable ? 'extension:enable' : 'extension:disable', extension.telemetryData); }); } @@ -521,6 +521,120 @@ export class ExtensionsWorkbenchService implements IExtensionsWorkbenchService { } + private promptAndSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise { + const allDependencies = this.getDependenciesRecursively(extension, this.local, enable, workspace, []); + if (allDependencies.length > 0) { + if (enable) { + return this.promptForDependenciesAndEnable(extension, allDependencies, workspace); + } else { + return this.promptForDependenciesAndDisable(extension, allDependencies, workspace); + } + } + return this.checkAndSetEnablement(extension, [], enable, workspace); + } + + private promptForDependenciesAndEnable(extension: IExtension, dependencies: IExtension[], workspace: boolean): TPromise { + const message = nls.localize('enableDependeciesConfirmation', "Enabling '{0}' also enable its dependencies. Would you like to continue?", extension.displayName); + const options = [ + nls.localize('enable', "Yes"), + nls.localize('doNotEnable', "No") + ]; + return this.choiceService.choose(Severity.Info, message, options, true) + .then(value => { + if (value === 0) { + return this.checkAndSetEnablement(extension, dependencies, true, workspace); + } + return TPromise.as(null); + }); + } + + private promptForDependenciesAndDisable(extension: IExtension, dependencies: IExtension[], workspace: boolean): TPromise { + const message = nls.localize('disableDependeciesConfirmation', "Would you like to disable '{0}' only or its dependencies also?", extension.displayName); + const options = [ + nls.localize('disableOnly', "Only"), + nls.localize('disableAll', "All"), + nls.localize('cancel', "Cancel") + ]; + return this.choiceService.choose(Severity.Info, message, options, true) + .then(value => { + if (value === 0) { + return this.checkAndSetEnablement(extension, [], false, workspace); + } + if (value === 1) { + return this.checkAndSetEnablement(extension, dependencies, false, workspace); + } + return TPromise.as(null); + }); + } + + private checkAndSetEnablement(extension: IExtension, dependencies: IExtension[], enable: boolean, workspace: boolean): TPromise { + if (!enable) { + let dependents = this.getDependentsAfterDisablement(extension, dependencies, this.local, workspace); + if (dependents.length) { + return TPromise.wrapError(this.getDependentsErrorMessage(extension, dependents)); + } + } + return TPromise.join([extension, ...dependencies].map(e => this.doSetEnablement(e, enable, workspace))); + } + + private getDependenciesRecursively(extension: IExtension, installed: IExtension[], enable: boolean, workspace: boolean, checked: IExtension[]): IExtension[] { + if (checked.indexOf(extension) !== -1) { + return []; + } + checked.push(extension); + if (!extension.dependencies || extension.dependencies.length === 0) { + return []; + } + const dependenciesToDisable = installed.filter(i => { + // Do not include extensions which are already disabled and request is to disable + if (!enable && (workspace ? i.disabledForWorkspace : i.disabledGlobally)) { + return false; + } + return extension.dependencies.indexOf(i.identifier) !== -1; + }); + const depsOfDeps = []; + for (const dep of dependenciesToDisable) { + depsOfDeps.push(...this.getDependenciesRecursively(dep, installed, enable, workspace, checked)); + } + return [...dependenciesToDisable, ...depsOfDeps]; + } + + private getDependentsAfterDisablement(extension: IExtension, dependencies: IExtension[], installed: IExtension[], workspace: boolean): IExtension[] { + return installed.filter(i => { + if (i.dependencies.length === 0) { + return false; + } + if (i === extension) { + return false; + } + const disabled = workspace ? i.disabledForWorkspace : i.disabledGlobally; + if (disabled) { + return false; + } + if (dependencies.indexOf(i) !== -1) { + return false; + } + return i.dependencies.some(dep => { + if (extension.identifier === dep) { + return true; + } + return dependencies.some(d => d.identifier === dep); + }); + }); + } + + private getDependentsErrorMessage(extension: IExtension, dependents: IExtension[]): string { + if (dependents.length === 1) { + return nls.localize('singleDependentError', "Cannot disable extension '{0}'. Extension '{1}' depends on this.", extension.displayName, dependents[0].displayName); + } + if (dependents.length === 2) { + return nls.localize('twoDependentsError', "Cannot disable extension '{0}'. Extensions '{1}' and '{2}' depend on this.", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + return nls.localize('multipleDependentsError', "Cannot disable extension '{0}'. Extensions '{1}', '{2}' and others depend on this.", + extension.displayName, dependents[0].displayName, dependents[1].displayName); + } + private doSetEnablement(extension: IExtension, enable: boolean, workspace: boolean): TPromise { if (workspace) { return this.extensionEnablementService.setEnablement(extension.identifier, enable, workspace); diff --git a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts index f69872b645c..7147b524286 100644 --- a/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts +++ b/src/vs/workbench/parts/extensions/test/electron-browser/extensionsWorkbenchService.test.ts @@ -28,6 +28,7 @@ import { IPager } from 'vs/base/common/paging'; import { ITelemetryService, NullTelemetryService } from 'vs/platform/telemetry/common/telemetry'; import { IWorkspaceContextService, WorkspaceContextService } from 'vs/platform/workspace/common/workspace'; import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; +import { IChoiceService } from 'vs/platform/message/common/message'; suite('ExtensionsWorkbenchService Test', () => { @@ -63,11 +64,14 @@ suite('ExtensionsWorkbenchService Test', () => { instantiationService.stub(IExtensionTipsService, ExtensionTipsService); instantiationService.stub(IExtensionTipsService, 'getKeymapRecommendations', () => []); + + instantiationService.stub(IChoiceService, { choose: () => null }); }); setup(() => { instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', []); instantiationService.stubPromise(IExtensionGalleryService, 'query', aPage()); + instantiationService.stubPromise(IChoiceService, 'choose', 0); (instantiationService.get(IExtensionEnablementService)).reset(); }); @@ -786,6 +790,156 @@ suite('ExtensionsWorkbenchService Test', () => { assert.ok(actual.disabledGlobally); }); + test('test disable extension with dependencies disable only itself', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + assert.ok(!testObject.local[1].disabledGlobally); + }); + + test('test disable extension with dependencies disable all', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + assert.ok(testObject.local[1].disabledGlobally); + }); + + test('test disable extension fails if extension is a dependent of other', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + return testObject.setEnablement(testObject.local[1], false).then(() => assert.fail('Should fail'), error => assert.ok(true)); + }); + + test('test disable extension does not fail if its dependency is a dependent of other but chosen to disable only itself', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + }); + + test('test disable extension fails if its dependency is a dependent of other', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + return testObject.setEnablement(testObject.local[0], false).then(() => assert.fail('Should fail'), error => assert.ok(true)); + }); + + test('test disable extension if its dependency is a dependent of other disabled extension', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + assert.ok(testObject.local[1].disabledGlobally); + }); + + test('test disable extension if its dependencys dependency is itself', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.a'] }), aLocalExtension('c')]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + assert.ok(testObject.local[1].disabledGlobally); + }); + + test('test disable extension if its dependency is dependent and is disabled', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c', { extensionDependencies: ['pub.b'] })]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + }); + + test('test disable extension with cyclic dependencies', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', true); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', true); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.c'] }), aLocalExtension('c', { extensionDependencies: ['pub.a'] })]); + instantiationService.stubPromise(IChoiceService, 'choose', 1); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], false); + + assert.ok(testObject.local[0].disabledGlobally); + assert.ok(testObject.local[1].disabledGlobally); + assert.ok(testObject.local[2].disabledGlobally); + }); + + test('test enable extension with dependencies enable all', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b'), aLocalExtension('c')]); + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], true); + + assert.ok(!testObject.local[0].disabledGlobally); + assert.ok(!testObject.local[1].disabledGlobally); + }); + + test('test enable extension with cyclic dependencies', () => { + instantiationService.get(IExtensionEnablementService).setEnablement('pub.a', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false); + instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); + instantiationService.stubPromise(IExtensionManagementService, 'getInstalled', [aLocalExtension('a', { extensionDependencies: ['pub.b'] }), aLocalExtension('b', { extensionDependencies: ['pub.c'] }), aLocalExtension('c', { extensionDependencies: ['pub.a'] })]); + + testObject = instantiationService.createInstance(ExtensionsWorkbenchService); + + testObject.setEnablement(testObject.local[0], true); + + assert.ok(!testObject.local[0].disabledGlobally); + assert.ok(!testObject.local[1].disabledGlobally); + assert.ok(!testObject.local[2].disabledGlobally); + }); + test('test change event is fired when disablement flags are changed', () => { instantiationService.get(IExtensionEnablementService).setEnablement('pub.c', false); instantiationService.get(IExtensionEnablementService).setEnablement('pub.b', false, true); @@ -814,7 +968,7 @@ suite('ExtensionsWorkbenchService Test', () => { function aLocalExtension(name: string = 'someext', manifest: any = {}, properties: any = {}): ILocalExtension { const localExtension = Object.create({ manifest: {} }); - assign(localExtension, { type: LocalExtensionType.User, id: generateUuid() }, properties); + assign(localExtension, { type: LocalExtensionType.User, id: generateUuid(), manifest: {} }, properties); assign(localExtension.manifest, { name, publisher: 'pub' }, manifest); localExtension.metadata = { id: localExtension.id, publisherId: localExtension.manifest.publisher, publisherDisplayName: 'somename' }; return localExtension; diff --git a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts index 38f317eaaea..954cea1054f 100644 --- a/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts +++ b/src/vs/workbench/parts/files/browser/editors/textFileEditor.ts @@ -206,8 +206,8 @@ export class TextFileEditor extends BaseTextEditor { return true; // in any case we handled it } - protected getCodeEditorOptions(): IEditorOptions { - const options = super.getCodeEditorOptions(); + protected getConfigurationOverrides(): IEditorOptions { + const options = super.getConfigurationOverrides(); const input = this.input; const inputName = input && input.getName(); diff --git a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts index 9b70ec70473..a87a915d26b 100644 --- a/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts +++ b/src/vs/workbench/parts/html/browser/htmlPreviewPart.ts @@ -72,7 +72,7 @@ export class HtmlPreviewPart extends BaseEditor { super.dispose(); } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { this._container = document.createElement('div'); this._container.style.paddingLeft = '20px'; parent.getHTMLElement().appendChild(this._container); @@ -101,7 +101,7 @@ export class HtmlPreviewPart extends BaseEditor { super.changePosition(position); } - public setEditorVisible(visible: boolean, position?: Position): void { + protected setEditorVisible(visible: boolean, position?: Position): void { this._doSetVisible(visible); super.setEditorVisible(visible, position); } diff --git a/src/vs/workbench/parts/output/browser/outputPanel.ts b/src/vs/workbench/parts/output/browser/outputPanel.ts index 3967a3dae21..9a1b48d3f51 100644 --- a/src/vs/workbench/parts/output/browser/outputPanel.ts +++ b/src/vs/workbench/parts/output/browser/outputPanel.ts @@ -75,8 +75,8 @@ export class OutputPanel extends TextResourceEditor { return super.getActionItem(action); } - protected getCodeEditorOptions(): IEditorOptions { - const options = super.getCodeEditorOptions(); + protected getConfigurationOverrides(): IEditorOptions { + const options = super.getConfigurationOverrides(); options.wrappingColumn = 0; // all output editors wrap options.lineNumbers = 'off'; // all output editors hide line numbers options.glyphMargin = false; @@ -96,7 +96,7 @@ export class OutputPanel extends TextResourceEditor { return super.setInput(input, options).then(() => this.revealLastLine()); } - public createEditor(parent: Builder): void { + protected createEditor(parent: Builder): void { // First create the scoped instantation service and only then construct the editor using the scoped service const scopedContextKeyService = this.contextKeyService.createScoped(parent.getHTMLElement()); diff --git a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts index 96c0a7da7ba..3b1e00ae280 100644 --- a/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts +++ b/src/vs/workbench/parts/preferences/browser/preferencesEditor.ts @@ -120,20 +120,20 @@ export class DefaultPreferencesEditor extends BaseTextEditor { this.delayedFilterLogging = new Delayer(1000); } - public createEditorControl(parent: Builder): editorCommon.IEditor { + public createEditorControl(parent: Builder, configuration: editorCommon.IEditorOptions): editorCommon.IEditor { const parentContainer = parent.getHTMLElement(); this.defaultSettingHeaderWidget = this._register(this.instantiationService.createInstance(DefaultSettingsHeaderWidget, parentContainer)); this._register(this.defaultSettingHeaderWidget.onDidChange(value => this.filterPreferences(value))); this._register(this.defaultSettingHeaderWidget.onEnter(value => this.focusNextPreference())); - const defaultPreferencesEditor = this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parentContainer, this.getCodeEditorOptions()); + const defaultPreferencesEditor = this.instantiationService.createInstance(DefaultPreferencesCodeEditor, parentContainer, configuration); return defaultPreferencesEditor; } - protected getCodeEditorOptions(): editorCommon.IEditorOptions { - const options = super.getCodeEditorOptions(); + protected getConfigurationOverrides(): editorCommon.IEditorOptions { + const options = super.getConfigurationOverrides(); options.readOnly = true; if (this.input) { options.lineNumbers = 'off'; diff --git a/src/vs/workbench/parts/search/browser/searchResultsView.ts b/src/vs/workbench/parts/search/browser/searchResultsView.ts index f08db80855d..d3355646c49 100644 --- a/src/vs/workbench/parts/search/browser/searchResultsView.ts +++ b/src/vs/workbench/parts/search/browser/searchResultsView.ts @@ -217,6 +217,8 @@ export class SearchAccessibilityProvider implements IAccessibilityProvider { export class SearchController extends DefaultController { + private _arrowKeyThrottler = new DumbThrottler(500); + constructor(private viewlet: SearchViewlet, @IInstantiationService private instantiationService: IInstantiationService) { super({ clickBehavior: ClickBehavior.ON_MOUSE_DOWN }); @@ -305,6 +307,10 @@ export class SearchController extends DefaultController { } private selectOnScroll(tree: ITree, focus: any, event: IKeyboardEvent): void { + this._arrowKeyThrottler.trigger(() => this.doSelectOnScroll(tree, focus, event)); + } + + private doSelectOnScroll(tree: ITree, focus: any, event: IKeyboardEvent): void { if (focus instanceof Match) { this.onEnter(tree, event); } else { @@ -313,6 +319,31 @@ export class SearchController extends DefaultController { } } +class DumbThrottler { + private waiting = false; + + private callback: Function; + + constructor(private timeout: number) { + } + + trigger(callback: Function): void { + if (this.waiting) { + this.callback = callback; + } else { + callback(); + this.waiting = true; + setTimeout(() => { + this.waiting = false; + if (this.callback) { + this.callback(); + this.callback = null; + } + }, this.timeout); + } + } +} + export class SearchFilter implements IFilter { public isVisible(tree: ITree, element: any): boolean { diff --git a/src/vs/workbench/parts/search/browser/searchViewlet.ts b/src/vs/workbench/parts/search/browser/searchViewlet.ts index 0b9507782e3..050e2799129 100644 --- a/src/vs/workbench/parts/search/browser/searchViewlet.ts +++ b/src/vs/workbench/parts/search/browser/searchViewlet.ts @@ -828,8 +828,8 @@ export class SearchViewlet extends Viewlet { private onQueryTriggered(query: ISearchQuery, excludePattern: string, includePattern: string): void { this.viewModel.cancelSearch(); - // Progress total is 100% - let progressTotal = 100; + // Progress total is 100.0% for more progress bar granularity + let progressTotal = 1000; let progressRunner = this.progressService.show(progressTotal); let progressWorked = 0; @@ -989,7 +989,7 @@ export class SearchViewlet extends Viewlet { // Progress bar update let fakeProgress = true; if (total > 0 && worked > 0) { - let ratio = Math.round((worked / total) * 100); + let ratio = Math.round((worked / total) * progressTotal); if (ratio > progressWorked) { // never show less progress than what we have already progressRunner.worked(ratio - progressWorked); progressWorked = ratio; @@ -997,11 +997,17 @@ export class SearchViewlet extends Viewlet { } } - // Fake progress up to 90% - if (fakeProgress && progressWorked < 90) { - progressWorked++; - progressRunner.worked(1); + // Fake progress up to 90%, or when actual progress beats it + const fakeMax = 900; + const fakeMultiplier = 15; + if (fakeProgress && progressWorked < fakeMax) { + // Linearly decrease the rate of fake progress. + // 1 is the smallest allowed amount of progress. + const fakeAmt = Math.round((fakeMax - progressWorked) / fakeMax * fakeMultiplier) || 1; + progressWorked += fakeAmt; + progressRunner.worked(fakeAmt); } + // Search result tree update let count = this.viewModel.searchResult.fileCount(); if (visibleMatches !== count) { @@ -1016,7 +1022,7 @@ export class SearchViewlet extends Viewlet { this.actionRegistry['vs.tree.collapse'].enabled = true; } } - }, 200); + }, 100); this.searchWidget.setReplaceAllActionState(false); // this.replaceService.disposeAllReplacePreviews(); diff --git a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css index 0befbdacb03..2fa59769fa7 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css +++ b/src/vs/workbench/parts/terminal/electron-browser/media/terminal.css @@ -37,6 +37,13 @@ height: 100%; } +.monaco-workbench .panel.integrated-terminal .xterm-viewport { + /* Subtract right margin so that the scroll bar aligns with size of panel */ + margin-right: -20px; + /* Force 100% height so that the scroll bar is aligned to the top and the bottom of the panel, the terminal rows may not be top aligned */ + height: 100% !important; +} + /* Terminal actions */ /* Light theme */ diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts index 6bfcc2aecef..d97c72fc963 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalInstance.ts @@ -373,12 +373,12 @@ export class TerminalInstance implements ITerminalInstance { this._isExiting = true; let exitCodeMessage: string; - if (exitCode !== 0) { + if (exitCode) { exitCodeMessage = nls.localize('terminal.integrated.exitedWithCode', 'The terminal process terminated with exit code: {0}', exitCode); } if (this._shellLaunchConfig.waitOnExit) { - if (exitCode !== 0) { + if (exitCode) { this._xterm.writeln(exitCodeMessage); } this._xterm.writeln(nls.localize('terminal.integrated.waitOnExit', 'Press any key to close the terminal')); @@ -389,7 +389,7 @@ export class TerminalInstance implements ITerminalInstance { }); } else { this.dispose(); - if (exitCode !== 0) { + if (exitCode) { if (this._isLaunching) { let args = ''; if (this._shellLaunchConfig.args && this._shellLaunchConfig.args.length) { diff --git a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts index 68fc66ae610..da6adf7cca8 100644 --- a/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts +++ b/src/vs/workbench/parts/terminal/electron-browser/terminalService.ts @@ -4,6 +4,7 @@ *--------------------------------------------------------------------------------------------*/ import Event, { Emitter } from 'vs/base/common/event'; +import * as errors from 'vs/base/common/errors'; import platform = require('vs/base/common/platform'); import { IContextKeyService, IContextKey } from 'vs/platform/contextkey/common/contextkey'; import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation'; @@ -190,7 +191,7 @@ export class TerminalService implements ITerminalService { public hidePanel(): void { const panel = this._panelService.getActivePanel(); if (panel && panel.getId() === TERMINAL_PANEL_ID) { - this._partService.setPanelHidden(true); + this._partService.setPanelHidden(true).done(undefined, errors.onUnexpectedError); } } diff --git a/src/vs/workbench/services/part/common/partService.ts b/src/vs/workbench/services/part/common/partService.ts index 5036d29cf88..47cca47baed 100644 --- a/src/vs/workbench/services/part/common/partService.ts +++ b/src/vs/workbench/services/part/common/partService.ts @@ -23,7 +23,6 @@ export enum Position { } export interface ILayoutOptions { - forceStyleRecompute?: boolean; toggleMaximizedPanel?: boolean; } @@ -80,12 +79,12 @@ export interface IPartService { /** * Set sidebar hidden or not */ - setSideBarHidden(hidden: boolean): void; + setSideBarHidden(hidden: boolean): TPromise; /** * Set panel part hidden or not */ - setPanelHidden(hidden: boolean): void; + setPanelHidden(hidden: boolean): TPromise; /** * Maximizes the panel height if the panel is not already maximized. diff --git a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts index 6ea355c39cc..22dc6e92a6b 100644 --- a/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts +++ b/src/vs/workbench/services/textfile/common/textFileEditorModelManager.ts @@ -321,9 +321,19 @@ export class TextFileEditorModelManager implements ITextFileEditorModelManager { } public getAll(resource?: URI): ITextFileEditorModel[] { - return Object.keys(this.mapResourceToModel) - .filter(r => !resource || resource.toString() === r) - .map(r => this.mapResourceToModel[r]); + if (resource) { + const res = this.mapResourceToModel[resource.toString()]; + + return res ? [res] : []; + } + + const keys = Object.keys(this.mapResourceToModel); + const res: ITextFileEditorModel[] = []; + for (let i = 0; i < keys.length; i++) { + res.push(this.mapResourceToModel[keys[i]]); + } + + return res; } public add(resource: URI, model: ITextFileEditorModel): void { diff --git a/src/vs/workbench/test/browser/part.test.ts b/src/vs/workbench/test/browser/part.test.ts index 06d23ce51f7..ffd2aeb186e 100644 --- a/src/vs/workbench/test/browser/part.test.ts +++ b/src/vs/workbench/test/browser/part.test.ts @@ -17,7 +17,7 @@ import { TestWorkspace } from 'vs/platform/workspace/test/common/testWorkspace'; class MyPart extends Part { constructor(private expectedParent: Builder) { - super('myPart'); + super('myPart', { hasTitle: true }); } public createTitleArea(parent: Builder): Builder { @@ -30,11 +30,6 @@ class MyPart extends Part { return super.createContentArea(parent); } - public createStatusArea(parent: Builder): Builder { - assert.strictEqual(parent, this.expectedParent); - return super.createStatusArea(parent); - } - public getMemento(storageService: IStorageService): any { return super.getMemento(storageService); } @@ -43,7 +38,7 @@ class MyPart extends Part { class MyPart2 extends Part { constructor() { - super('myPart2'); + super('myPart2', { hasTitle: true }); } public createTitleArea(parent: Builder): Builder { @@ -63,21 +58,12 @@ class MyPart2 extends Part { }); }); } - - public createStatusArea(parent: Builder): Builder { - return parent.div(function (div) { - div.span({ - id: 'myPart.status', - innerHtml: 'Status' - }); - }); - } } class MyPart3 extends Part { constructor() { - super('myPart2'); + super('myPart2', { hasTitle: false }); } public createTitleArea(parent: Builder): Builder { @@ -92,10 +78,6 @@ class MyPart3 extends Part { }); }); } - - public createStatusArea(parent: Builder): Builder { - return null; - } } suite('Workbench Part', () => { @@ -153,7 +135,7 @@ suite('Workbench Part', () => { assert.strictEqual(Types.isEmptyObject(memento), true); }); - test('Part Layout with Title, Content and Status', function () { + test('Part Layout with Title and Content', function () { let b = Build.withElementById(fixtureId); b.div().hide(); @@ -162,7 +144,6 @@ suite('Workbench Part', () => { assert(Build.withElementById('myPart.title')); assert(Build.withElementById('myPart.content')); - assert(Build.withElementById('myPart.status')); }); test('Part Layout with Content only', function () { @@ -174,6 +155,5 @@ suite('Workbench Part', () => { assert(!Build.withElementById('myPart.title')); assert(Build.withElementById('myPart.content')); - assert(!Build.withElementById('myPart.status')); }); }); \ No newline at end of file diff --git a/src/vs/workbench/test/workbenchTestServices.ts b/src/vs/workbench/test/workbenchTestServices.ts index 22652887145..a336de3b3e7 100644 --- a/src/vs/workbench/test/workbenchTestServices.ts +++ b/src/vs/workbench/test/workbenchTestServices.ts @@ -272,13 +272,13 @@ export class TestPartService implements IPartService { return false; } - public setSideBarHidden(hidden: boolean): void { } + public setSideBarHidden(hidden: boolean): TPromise { return TPromise.as(null); } public isPanelHidden(): boolean { return false; } - public setPanelHidden(hidden: boolean): void { } + public setPanelHidden(hidden: boolean): TPromise { return TPromise.as(null); } public toggleMaximizedPanel(): void { }