Skip to content

Commit 88663bd

Browse files
committed
add keyboard command for ctrl+hover
fixes microsoft#46257 refs microsoft#59260
1 parent a3837d1 commit 88663bd

4 files changed

Lines changed: 125 additions & 38 deletions

File tree

src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.css renamed to src/vs/editor/contrib/goToDefinition/goToDefinitionAtPosition.css

File renamed without changes.

src/vs/editor/contrib/goToDefinition/goToDefinitionMouse.ts renamed to src/vs/editor/contrib/goToDefinition/goToDefinitionAtPosition.ts

Lines changed: 81 additions & 37 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import { IModeService } from 'vs/editor/common/services/modeService';
1313
import { Range, IRange } from 'vs/editor/common/core/range';
1414
import * as editorCommon from 'vs/editor/common/editorCommon';
1515
import { DefinitionProviderRegistry, LocationLink } from 'vs/editor/common/modes';
16-
import { ICodeEditor, IMouseTarget, MouseTargetType } from 'vs/editor/browser/editorBrowser';
16+
import { ICodeEditor, MouseTargetType } from 'vs/editor/browser/editorBrowser';
1717
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
1818
import { getDefinitionsAtPosition } from './goToDefinition';
1919
import { DisposableStore } from 'vs/base/common/lifecycle';
@@ -26,16 +26,19 @@ import { ClickLinkGesture, ClickLinkMouseEvent, ClickLinkKeyboardEvent } from 'v
2626
import { IWordAtPosition, IModelDeltaDecoration, ITextModel, IFoundBracket } from 'vs/editor/common/model';
2727
import { Position } from 'vs/editor/common/core/position';
2828
import { withNullAsUndefined } from 'vs/base/common/types';
29+
import { IKeyboardEvent } from 'vs/base/browser/keyboardEvent';
30+
import { KeyCode } from 'vs/base/common/keyCodes';
2931

30-
class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorContribution {
32+
export class GotoDefinitionAtPositionEditorContribution implements editorCommon.IEditorContribution {
3133

3234
public static readonly ID = 'editor.contrib.gotodefinitionwithmouse';
3335
static readonly MAX_SOURCE_PREVIEW_LINES = 8;
3436

3537
private readonly editor: ICodeEditor;
3638
private readonly toUnhook = new DisposableStore();
37-
private decorations: string[] = [];
38-
private currentWordUnderMouse: IWordAtPosition | null = null;
39+
private readonly toUnhookForKeyboard = new DisposableStore();
40+
private linkDecorations: string[] = [];
41+
private currentWordAtPosition: IWordAtPosition | null = null;
3942
private previousPromise: CancelablePromise<LocationLink[] | null> | null = null;
4043

4144
constructor(
@@ -49,55 +52,96 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
4952
this.toUnhook.add(linkGesture);
5053

5154
this.toUnhook.add(linkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => {
52-
this.startFindDefinition(mouseEvent, withNullAsUndefined(keyboardEvent));
55+
this.startFindDefinitionFromMouse(mouseEvent, withNullAsUndefined(keyboardEvent));
5356
}));
5457

5558
this.toUnhook.add(linkGesture.onExecute((mouseEvent: ClickLinkMouseEvent) => {
5659
if (this.isEnabled(mouseEvent)) {
57-
this.gotoDefinition(mouseEvent.target, mouseEvent.hasSideBySideModifier).then(() => {
58-
this.removeDecorations();
60+
this.gotoDefinition(mouseEvent.target.position!, mouseEvent.hasSideBySideModifier).then(() => {
61+
this.removeLinkDecorations();
5962
}, (error: Error) => {
60-
this.removeDecorations();
63+
this.removeLinkDecorations();
6164
onUnexpectedError(error);
6265
});
6366
}
6467
}));
6568

6669
this.toUnhook.add(linkGesture.onCancel(() => {
67-
this.removeDecorations();
68-
this.currentWordUnderMouse = null;
70+
this.removeLinkDecorations();
71+
this.currentWordAtPosition = null;
6972
}));
73+
}
74+
75+
static get(editor: ICodeEditor): GotoDefinitionAtPositionEditorContribution {
76+
return editor.getContribution<GotoDefinitionAtPositionEditorContribution>(GotoDefinitionAtPositionEditorContribution.ID);
77+
}
7078

79+
startFindDefinitionFromCursor(position: Position) {
80+
// For issue: https://github.com/microsoft/vscode/issues/46257
81+
// equivalent to mouse move with meta/ctrl key
82+
83+
// First find the definition and add decorations
84+
// to the editor to be shown with the content hover widget
85+
return this.startFindDefinition(position).then(() => {
86+
87+
// Add listeners for editor cursor move and key down events
88+
// Dismiss the "extended" editor decorations when the user hides
89+
// the hover widget. There is no event for the widget itself so these
90+
// serve as a best effort. After removing the link decorations, the hover
91+
// widget is clean and will only show declarations per next request.
92+
this.toUnhookForKeyboard.add(this.editor.onDidChangeCursorPosition(() => {
93+
this.currentWordAtPosition = null;
94+
this.removeLinkDecorations();
95+
this.toUnhookForKeyboard.clear();
96+
}));
97+
98+
this.toUnhookForKeyboard.add(this.editor.onKeyDown((e: IKeyboardEvent) => {
99+
if (e) {
100+
this.currentWordAtPosition = null;
101+
this.removeLinkDecorations();
102+
this.toUnhookForKeyboard.clear();
103+
}
104+
}));
105+
});
71106
}
72107

73-
private startFindDefinition(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void {
108+
private startFindDefinitionFromMouse(mouseEvent: ClickLinkMouseEvent, withKey?: ClickLinkKeyboardEvent): void {
74109

75110
// check if we are active and on a content widget
76-
if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.decorations.length > 0) {
111+
if (mouseEvent.target.type === MouseTargetType.CONTENT_WIDGET && this.linkDecorations.length > 0) {
77112
return;
78113
}
79114

80115
if (!this.editor.hasModel() || !this.isEnabled(mouseEvent, withKey)) {
81-
this.currentWordUnderMouse = null;
82-
this.removeDecorations();
116+
this.currentWordAtPosition = null;
117+
this.removeLinkDecorations();
83118
return;
84119
}
85120

121+
const position = mouseEvent.target.position!;
122+
123+
this.startFindDefinition(position);
124+
}
125+
126+
private startFindDefinition(position: Position): Promise<number | undefined> {
127+
128+
// Dispose listeners for updating decorations when using keyboard to show definition hover
129+
this.toUnhookForKeyboard.clear();
130+
86131
// Find word at mouse position
87-
const word = mouseEvent.target.position ? this.editor.getModel().getWordAtPosition(mouseEvent.target.position) : null;
132+
const word = position ? this.editor.getModel()?.getWordAtPosition(position) : null;
88133
if (!word) {
89-
this.currentWordUnderMouse = null;
90-
this.removeDecorations();
91-
return;
134+
this.currentWordAtPosition = null;
135+
this.removeLinkDecorations();
136+
return Promise.resolve(0);
92137
}
93-
const position = mouseEvent.target.position!;
94138

95139
// Return early if word at position is still the same
96-
if (this.currentWordUnderMouse && this.currentWordUnderMouse.startColumn === word.startColumn && this.currentWordUnderMouse.endColumn === word.endColumn && this.currentWordUnderMouse.word === word.word) {
97-
return;
140+
if (this.currentWordAtPosition && this.currentWordAtPosition.startColumn === word.startColumn && this.currentWordAtPosition.endColumn === word.endColumn && this.currentWordAtPosition.word === word.word) {
141+
return Promise.resolve(0);
98142
}
99143

100-
this.currentWordUnderMouse = word;
144+
this.currentWordAtPosition = word;
101145

102146
// Find definition and decorate word if found
103147
let state = new EditorState(this.editor, CodeEditorStateFlag.Position | CodeEditorStateFlag.Value | CodeEditorStateFlag.Selection | CodeEditorStateFlag.Scroll);
@@ -107,11 +151,11 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
107151
this.previousPromise = null;
108152
}
109153

110-
this.previousPromise = createCancelablePromise(token => this.findDefinition(mouseEvent.target, token));
154+
this.previousPromise = createCancelablePromise(token => this.findDefinition(position, token));
111155

112-
this.previousPromise.then(results => {
156+
return this.previousPromise.then(results => {
113157
if (!results || !results.length || !state.validate(this.editor)) {
114-
this.removeDecorations();
158+
this.removeLinkDecorations();
115159
return;
116160
}
117161

@@ -170,7 +214,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
170214
private getPreviewValue(textEditorModel: ITextModel, startLineNumber: number, result: LocationLink) {
171215
let rangeToUse = result.targetSelectionRange ? result.range : this.getPreviewRangeBasedOnBrackets(textEditorModel, startLineNumber);
172216
const numberOfLinesInRange = rangeToUse.endLineNumber - rangeToUse.startLineNumber;
173-
if (numberOfLinesInRange >= GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES) {
217+
if (numberOfLinesInRange >= GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES) {
174218
rangeToUse = this.getPreviewRangeBasedOnIndentation(textEditorModel, startLineNumber);
175219
}
176220

@@ -193,7 +237,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
193237

194238
private getPreviewRangeBasedOnIndentation(textEditorModel: ITextModel, startLineNumber: number) {
195239
const startIndent = textEditorModel.getLineFirstNonWhitespaceColumn(startLineNumber);
196-
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES);
240+
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES);
197241
let endLineNumber = startLineNumber + 1;
198242

199243
for (; endLineNumber < maxLineNumber; endLineNumber++) {
@@ -208,7 +252,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
208252
}
209253

210254
private getPreviewRangeBasedOnBrackets(textEditorModel: ITextModel, startLineNumber: number) {
211-
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionWithMouseEditorContribution.MAX_SOURCE_PREVIEW_LINES);
255+
const maxLineNumber = Math.min(textEditorModel.getLineCount(), startLineNumber + GotoDefinitionAtPositionEditorContribution.MAX_SOURCE_PREVIEW_LINES);
212256

213257
const brackets: IFoundBracket[] = [];
214258

@@ -263,12 +307,12 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
263307
}
264308
};
265309

266-
this.decorations = this.editor.deltaDecorations(this.decorations, [newDecorations]);
310+
this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, [newDecorations]);
267311
}
268312

269-
private removeDecorations(): void {
270-
if (this.decorations.length > 0) {
271-
this.decorations = this.editor.deltaDecorations(this.decorations, []);
313+
private removeLinkDecorations(): void {
314+
if (this.linkDecorations.length > 0) {
315+
this.linkDecorations = this.editor.deltaDecorations(this.linkDecorations, []);
272316
}
273317
}
274318

@@ -280,17 +324,17 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
280324
DefinitionProviderRegistry.has(this.editor.getModel());
281325
}
282326

283-
private findDefinition(target: IMouseTarget, token: CancellationToken): Promise<LocationLink[] | null> {
327+
private findDefinition(position: Position, token: CancellationToken): Promise<LocationLink[] | null> {
284328
const model = this.editor.getModel();
285329
if (!model) {
286330
return Promise.resolve(null);
287331
}
288332

289-
return getDefinitionsAtPosition(model, target.position!, token);
333+
return getDefinitionsAtPosition(model, position, token);
290334
}
291335

292-
private gotoDefinition(target: IMouseTarget, sideBySide: boolean): Promise<any> {
293-
this.editor.setPosition(target.position!);
336+
private gotoDefinition(position: Position, sideBySide: boolean): Promise<any> {
337+
this.editor.setPosition(position);
294338
const action = new DefinitionAction(new DefinitionActionConfig(sideBySide, false, true, false), { alias: '', label: '', id: '', precondition: undefined });
295339
return this.editor.invokeWithinContext(accessor => action.run(accessor, this.editor));
296340
}
@@ -300,7 +344,7 @@ class GotoDefinitionWithMouseEditorContribution implements editorCommon.IEditorC
300344
}
301345
}
302346

303-
registerEditorContribution(GotoDefinitionWithMouseEditorContribution.ID, GotoDefinitionWithMouseEditorContribution);
347+
registerEditorContribution(GotoDefinitionAtPositionEditorContribution.ID, GotoDefinitionAtPositionEditorContribution);
304348

305349
registerThemingParticipant((theme, collector) => {
306350
const activeLinkForeground = theme.getColor(editorActiveLinkForeground);

src/vs/editor/contrib/hover/hover.ts

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,7 @@ import { IThemeService, registerThemingParticipant } from 'vs/platform/theme/com
2626
import { IMarkerDecorationsService } from 'vs/editor/common/services/markersDecorationService';
2727
import { IKeybindingService } from 'vs/platform/keybinding/common/keybinding';
2828
import { AccessibilitySupport } from 'vs/platform/accessibility/common/accessibility';
29+
import { GotoDefinitionAtPositionEditorContribution } from 'vs/editor/contrib/goToDefinition/goToDefinitionAtPosition';
2930

3031
export class ModesHoverController implements IEditorContribution {
3132

@@ -258,8 +259,50 @@ class ShowHoverAction extends EditorAction {
258259
}
259260
}
260261

262+
class ShowCtrlHoverAction extends EditorAction {
263+
264+
constructor() {
265+
super({
266+
id: 'editor.action.showCtrlHover',
267+
label: nls.localize({
268+
key: 'showCtrlHover',
269+
comment: [
270+
'Label for action that will trigger the showing of a ctrl+hover in the editor.',
271+
'This allows for users to show the ctrl+hover without using the mouse.'
272+
]
273+
}, "Show Control Hover"),
274+
alias: 'Show Control Hover',
275+
precondition: undefined
276+
});
277+
}
278+
279+
public run(accessor: ServicesAccessor, editor: ICodeEditor): void {
280+
let controller = ModesHoverController.get(editor);
281+
if (!controller) {
282+
return;
283+
}
284+
const position = editor.getPosition();
285+
286+
if (!position) {
287+
return;
288+
}
289+
290+
const range = new Range(position.lineNumber, position.column, position.lineNumber, position.column);
291+
const goto = GotoDefinitionAtPositionEditorContribution.get(editor);
292+
const promise = goto.startFindDefinitionFromCursor(position);
293+
if (promise) {
294+
promise.then(() => {
295+
controller.showContentHover(range, HoverStartMode.Immediate, true);
296+
});
297+
} else {
298+
controller.showContentHover(range, HoverStartMode.Immediate, true);
299+
}
300+
}
301+
}
302+
261303
registerEditorContribution(ModesHoverController.ID, ModesHoverController);
262304
registerEditorAction(ShowHoverAction);
305+
registerEditorAction(ShowCtrlHoverAction);
263306

264307
// theming
265308
registerThemingParticipant((theme, collector) => {

src/vs/editor/editor.all.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ import 'vs/editor/contrib/folding/folding';
2323
import 'vs/editor/contrib/fontZoom/fontZoom';
2424
import 'vs/editor/contrib/format/formatActions';
2525
import 'vs/editor/contrib/goToDefinition/goToDefinitionCommands';
26-
import 'vs/editor/contrib/goToDefinition/goToDefinitionMouse';
26+
import 'vs/editor/contrib/goToDefinition/goToDefinitionAtPosition';
2727
import 'vs/editor/contrib/gotoError/gotoError';
2828
import 'vs/editor/contrib/hover/hover';
2929
import 'vs/editor/contrib/inPlaceReplace/inPlaceReplace';

0 commit comments

Comments
 (0)