diff --git a/src/vs/base/common/async.ts b/src/vs/base/common/async.ts index ab03fa74f39..3528257220f 100644 --- a/src/vs/base/common/async.ts +++ b/src/vs/base/common/async.ts @@ -9,7 +9,7 @@ import * as errors from 'vs/base/common/errors'; import * as platform from 'vs/base/common/platform'; import { Promise, TPromise, ValueCallback, ErrorCallback, ProgressCallback } from 'vs/base/common/winjs.base'; import { CancellationToken, CancellationTokenSource } from 'vs/base/common/cancellation'; -import { Disposable } from 'vs/base/common/lifecycle'; +import { Disposable, IDisposable } from 'vs/base/common/lifecycle'; import Event, { Emitter } from 'vs/base/common/event'; function isThenable(obj: any): obj is Thenable { @@ -501,6 +501,11 @@ export class Queue extends Limiter { } } +export function setDisposableTimeout(handler: Function, timeout: number, ...args: any[]): IDisposable { + const handle = setTimeout(handler, timeout, ...args); + return { dispose() { clearTimeout(handle); } }; +} + export class TimeoutTimer extends Disposable { private _token: platform.TimeoutToken; @@ -642,4 +647,4 @@ export function ninvoke(thisArg: any, fn: Function, ...args: any[]): Promise; export function ninvoke(thisArg: any, fn: Function, ...args: any[]): TPromise; export function ninvoke(thisArg: any, fn: Function, ...args: any[]): any { return new Promise((c, e) => fn.call(thisArg, ...args, (err, result) => err ? e(err) : c(result))); -} \ No newline at end of file +} diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css index 2493cde3d82..058ef445e0f 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.css @@ -3,27 +3,6 @@ * Licensed under the MIT License. See License.txt in the project root for license information. *--------------------------------------------------------------------------------------------*/ -.monaco-editor .monaco-editor-overlaymessage { - padding-bottom: 8px; -} - -.monaco-editor .monaco-editor-overlaymessage .message { - padding: 1px 4px; - background: #1382CE; - color: white; -} - -.monaco-editor .monaco-editor-overlaymessage .anchor { - width: 0 !important; - height: 0 !important; - border-color: transparent; - border-style: solid; - z-index: 1000; - border-width: 8px; - border-top-color: #1382CE; - position: absolute; -} - .monaco-editor .preview-zone-widget.preview .monaco-editor, .monaco-editor .preview-zone-widget.preview .monaco-editor-background, .monaco-editor .preview-zone-widget.preview .monaco-editor .line-numbers, diff --git a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts index ec90cef7f0d..7f106811f8b 100644 --- a/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts +++ b/src/vs/editor/contrib/goToDeclaration/browser/goToDeclaration.ts @@ -8,7 +8,6 @@ import 'vs/css!./goToDeclaration'; import * as nls from 'vs/nls'; import { Throttler } from 'vs/base/common/async'; -import { any, filterEvent } from 'vs/base/common/event'; import { onUnexpectedError } from 'vs/base/common/errors'; import { MarkedString } from 'vs/base/common/htmlContent'; import { KeyCode, KeyMod, KeyChord } from 'vs/base/common/keyCodes'; @@ -25,7 +24,7 @@ import { Range } from 'vs/editor/common/core/range'; import * as editorCommon from 'vs/editor/common/editorCommon'; import { editorAction, IActionOptions, ServicesAccessor, EditorAction } from 'vs/editor/common/editorCommonExtensions'; import { Location, DefinitionProviderRegistry } from 'vs/editor/common/modes'; -import { ICodeEditor, IEditorMouseEvent, IMouseTarget, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { ICodeEditor, IEditorMouseEvent, IMouseTarget } from 'vs/editor/browser/editorBrowser'; import { editorContribution } from 'vs/editor/browser/editorBrowserExtensions'; import { getDefinitionsAtPosition, getImplementationsAtPosition, getTypeDefinitionsAtPosition } from 'vs/editor/contrib/goToDeclaration/common/goToDeclaration'; import { ReferencesController } from 'vs/editor/contrib/referenceSearch/browser/referencesController'; @@ -34,91 +33,17 @@ import { IDisposable, dispose } from 'vs/base/common/lifecycle'; import { PeekContext } from 'vs/editor/contrib/zoneWidget/browser/peekViewWidget'; import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey'; import { ITextModelResolverService } from 'vs/editor/common/services/resolverService'; +import { MessageController } from './messageController'; import * as corePosition from 'vs/editor/common/core/position'; import ModeContextKeys = editorCommon.ModeContextKeys; import EditorContextKeys = editorCommon.EditorContextKeys; -class MessageOverlay implements IContentWidget { - - private static _last: IDisposable; - - static show(editor: ICodeEditor, pos: editorCommon.IPosition, message: string): void { - - dispose(MessageOverlay._last); - - const widget = new MessageOverlay(message, pos.lineNumber, editor.getOffsetForColumn(pos.lineNumber, pos.column)); - const remove = () => editor.removeContentWidget(widget); - editor.addContentWidget(widget); - - const listener = any( - filterEvent(editor.onMouseMove, e => { - const { position } = e.target; - return Math.abs(position.lineNumber - pos.lineNumber) > 1 - || Math.abs(position.column - pos.column) > 10; - }), - editor.onDidChangeCursorPosition, - editor.onDidBlurEditorText, - editor.onDidDispose, - editor.onDidChangeModel - )(_ => { - listener.dispose(); - remove(); - }); - - - MessageOverlay._last = { - dispose() { - listener.dispose(); - remove(); - } - }; - } - - readonly allowEditorOverflow = true; - readonly suppressMouseDown = false; - - private _position: editorCommon.IPosition; - private _domNode: HTMLDivElement; - - constructor(text: string, lineNumber: number, horizontalOffset: number) { - this._position = { lineNumber, column: 1 }; - - this._domNode = document.createElement('div'); - this._domNode.style.paddingLeft = `${horizontalOffset - 6}px`; - this._domNode.classList.add('monaco-editor-overlaymessage'); - - - const message = document.createElement('div'); - message.classList.add('message'); - message.textContent = text; - this._domNode.appendChild(message); - - const anchor = document.createElement('div'); - anchor.classList.add('anchor'); - this._domNode.appendChild(anchor); - } - - getId(): string { - return 'messageoverlay'; - } - - getDomNode(): HTMLElement { - return this._domNode; - } - - getPosition(): IContentWidgetPosition { - return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] }; - } -} - - export class DefinitionActionConfig { constructor( public openToSide = false, public openInPeek = false, - public filterCurrent = true, - public noResultsMessage = nls.localize('generic.noResults', "No definition found") + public filterCurrent = true ) { // } @@ -164,7 +89,12 @@ export class DefinitionAction extends EditorAction { } if (result.length === 0) { - MessageOverlay.show(editor, pos, this._configuration.noResultsMessage); + const info = model.getWordAtPosition(pos); + const message = info && info.word + ? nls.localize('noResultWord', "No definition found for '{0}'", info.word) + : nls.localize('generic.noResults', "No definition found"); + + MessageController.get(editor).showMessage(message, pos); return; } diff --git a/src/vs/editor/contrib/goToDeclaration/browser/messageController.css b/src/vs/editor/contrib/goToDeclaration/browser/messageController.css new file mode 100644 index 00000000000..1d73b5ace4c --- /dev/null +++ b/src/vs/editor/contrib/goToDeclaration/browser/messageController.css @@ -0,0 +1,25 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +.monaco-editor .monaco-editor-overlaymessage { + padding-bottom: 8px; +} + +.monaco-editor .monaco-editor-overlaymessage .message { + padding: 1px 4px; + background: #1382CE; + color: white; +} + +.monaco-editor .monaco-editor-overlaymessage .anchor { + width: 0 !important; + height: 0 !important; + border-color: transparent; + border-style: solid; + z-index: 1000; + border-width: 8px; + border-top-color: #1382CE; + position: absolute; +} diff --git a/src/vs/editor/contrib/goToDeclaration/browser/messageController.ts b/src/vs/editor/contrib/goToDeclaration/browser/messageController.ts new file mode 100644 index 00000000000..fcfb879f0e7 --- /dev/null +++ b/src/vs/editor/contrib/goToDeclaration/browser/messageController.ts @@ -0,0 +1,150 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +'use strict'; + +import 'vs/css!./messageController'; +import { any } from 'vs/base/common/event'; +import { setDisposableTimeout } from 'vs/base/common/async'; +import { KeyCode } from 'vs/base/common/keyCodes'; +import { IDisposable, dispose } from 'vs/base/common/lifecycle'; +import { Range } from 'vs/editor/common/core/range'; +import * as editorCommon from 'vs/editor/common/editorCommon'; +import { commonEditorContribution, CommonEditorRegistry, EditorCommand } from 'vs/editor/common/editorCommonExtensions'; +import { ICodeEditor, IContentWidget, IContentWidgetPosition, ContentWidgetPositionPreference } from 'vs/editor/browser/editorBrowser'; +import { IContextKeyService, RawContextKey, IContextKey } from 'vs/platform/contextkey/common/contextkey'; + +@commonEditorContribution +export class MessageController { + + private static _id = 'editor.contrib.messageController'; + + static CONTEXT_SNIPPET_MODE = new RawContextKey('messageVisible', false); + + static get(editor: editorCommon.ICommonCodeEditor): MessageController { + return editor.getContribution(MessageController._id); + } + + getId(): string { + return MessageController._id; + } + + private _editor: ICodeEditor; + private _visible: IContextKey; + private _messageListeners: IDisposable[] = []; + + constructor( + editor: ICodeEditor, + @IContextKeyService contextKeyService: IContextKeyService + ) { + this._editor = editor; + this._visible = MessageController.CONTEXT_SNIPPET_MODE.bindTo(contextKeyService); + } + + dispose() { + this._visible.reset(); + } + + showMessage(message: string, position: editorCommon.IPosition): void { + + this._visible.set(true); + this._messageListeners = dispose(this._messageListeners); + this._messageListeners.push(new MessageWidget(this._editor, position, message)); + + // close on blur, cursor, model change, dispose + this._messageListeners.push(any( + this._editor.onDidBlurEditorText, + this._editor.onDidChangeCursorPosition, + this._editor.onDidDispose, + this._editor.onDidChangeModel + )(this.closeMessage, this)); + + // close after 3s + this._messageListeners.push(setDisposableTimeout(() => this.closeMessage(), 3000)); + + // close on mouse move + let bounds: Range; + this._messageListeners.push(this._editor.onMouseMove(e => { + // outside the text area + if (!e.target.position) { + return; + } + + if (!bounds) { + // define bounding box around position and first mouse occurance + bounds = new Range(position.lineNumber - 3, 1, e.target.position.lineNumber + 3, 1); + } else if (!bounds.containsPosition(e.target.position)) { + // check if position is still in bounds + this.closeMessage(); + } + })); + } + + closeMessage(): void { + this._visible.reset(); + this._messageListeners = dispose(this._messageListeners); + } +} + +const MessageCommand = EditorCommand.bindToContribution(MessageController.get); + + +CommonEditorRegistry.registerEditorCommand(new MessageCommand({ + id: 'leaveEditorMessage', + precondition: MessageController.CONTEXT_SNIPPET_MODE, + handler: c => c.closeMessage(), + kbOpts: { + weight: CommonEditorRegistry.commandWeight(30), + primary: KeyCode.Escape + } +})); + +class MessageWidget implements IContentWidget { + + readonly allowEditorOverflow = true; + readonly suppressMouseDown = false; + + private _editor: ICodeEditor; + private _position: editorCommon.IPosition; + private _domNode: HTMLDivElement; + + constructor(editor: ICodeEditor, { lineNumber, column }: editorCommon.IPosition, text: string) { + + this._editor = editor; + this._editor.revealLinesInCenterIfOutsideViewport(lineNumber, lineNumber); + this._position = { lineNumber, column: 1 }; + + this._domNode = document.createElement('div'); + this._domNode.style.paddingLeft = `${editor.getOffsetForColumn(lineNumber, column) - 6}px`; + this._domNode.classList.add('monaco-editor-overlaymessage'); + + const message = document.createElement('div'); + message.classList.add('message'); + message.textContent = text; + this._domNode.appendChild(message); + + const anchor = document.createElement('div'); + anchor.classList.add('anchor'); + this._domNode.appendChild(anchor); + + this._editor.addContentWidget(this); + } + + dispose() { + this._editor.removeContentWidget(this); + } + + getId(): string { + return 'messageoverlay'; + } + + getDomNode(): HTMLElement { + return this._domNode; + } + + getPosition(): IContentWidgetPosition { + return { position: this._position, preference: [ContentWidgetPositionPreference.ABOVE] }; + } +}