Skip to content

Commit 771bfcf

Browse files
authored
Long Distance Hints (#274700)
* first version of NES long distance hints * Updates monaco.d.ts
1 parent 3d87a13 commit 771bfcf

File tree

24 files changed

+1363
-103
lines changed

24 files changed

+1363
-103
lines changed

src/vs/base/browser/dom.ts

Lines changed: 37 additions & 35 deletions
Original file line numberDiff line numberDiff line change
@@ -2507,14 +2507,51 @@ export abstract class ObserverNode<T extends HTMLOrSVGElement = HTMLOrSVGElement
25072507
this.keepUpdated(store);
25082508
return new LiveElement(this._element, store);
25092509
}
2510+
2511+
private _isHovered: IObservable<boolean> | undefined = undefined;
2512+
2513+
get isHovered(): IObservable<boolean> {
2514+
if (!this._isHovered) {
2515+
const hovered = observableValue<boolean>('hovered', false);
2516+
this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined));
2517+
this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined));
2518+
this._isHovered = hovered;
2519+
}
2520+
return this._isHovered;
2521+
}
2522+
2523+
private _didMouseMoveDuringHover: IObservable<boolean> | undefined = undefined;
2524+
2525+
get didMouseMoveDuringHover(): IObservable<boolean> {
2526+
if (!this._didMouseMoveDuringHover) {
2527+
let _hovering = false;
2528+
const hovered = observableValue<boolean>('didMouseMoveDuringHover', false);
2529+
this._element.addEventListener('mouseenter', (_e) => {
2530+
_hovering = true;
2531+
});
2532+
this._element.addEventListener('mousemove', (_e) => {
2533+
if (_hovering) {
2534+
hovered.set(true, undefined);
2535+
}
2536+
});
2537+
this._element.addEventListener('mouseleave', (_e) => {
2538+
_hovering = false;
2539+
hovered.set(false, undefined);
2540+
});
2541+
this._didMouseMoveDuringHover = hovered;
2542+
}
2543+
return this._didMouseMoveDuringHover;
2544+
}
25102545
}
2546+
25112547
function setClassName(domNode: HTMLOrSVGElement, className: string) {
25122548
if (isSVGElement(domNode)) {
25132549
domNode.setAttribute('class', className);
25142550
} else {
25152551
domNode.className = className;
25162552
}
25172553
}
2554+
25182555
function resolve<T>(value: ValueOrList<T>, reader: IReader | undefined, cb: (val: T) => void): void {
25192556
if (isObservable(value)) {
25202557
cb(value.read(reader));
@@ -2582,41 +2619,6 @@ export class ObserverNodeWithElement<T extends HTMLOrSVGElement = HTMLOrSVGEleme
25822619
public get element() {
25832620
return this._element;
25842621
}
2585-
2586-
private _isHovered: IObservable<boolean> | undefined = undefined;
2587-
2588-
get isHovered(): IObservable<boolean> {
2589-
if (!this._isHovered) {
2590-
const hovered = observableValue<boolean>('hovered', false);
2591-
this._element.addEventListener('mouseenter', (_e) => hovered.set(true, undefined));
2592-
this._element.addEventListener('mouseleave', (_e) => hovered.set(false, undefined));
2593-
this._isHovered = hovered;
2594-
}
2595-
return this._isHovered;
2596-
}
2597-
2598-
private _didMouseMoveDuringHover: IObservable<boolean> | undefined = undefined;
2599-
2600-
get didMouseMoveDuringHover(): IObservable<boolean> {
2601-
if (!this._didMouseMoveDuringHover) {
2602-
let _hovering = false;
2603-
const hovered = observableValue<boolean>('didMouseMoveDuringHover', false);
2604-
this._element.addEventListener('mouseenter', (_e) => {
2605-
_hovering = true;
2606-
});
2607-
this._element.addEventListener('mousemove', (_e) => {
2608-
if (_hovering) {
2609-
hovered.set(true, undefined);
2610-
}
2611-
});
2612-
this._element.addEventListener('mouseleave', (_e) => {
2613-
_hovering = false;
2614-
hovered.set(false, undefined);
2615-
});
2616-
this._didMouseMoveDuringHover = hovered;
2617-
}
2618-
return this._didMouseMoveDuringHover;
2619-
}
26202622
}
26212623
function setOrRemoveAttribute(element: HTMLOrSVGElement, key: string, value: unknown) {
26222624
if (value === null || value === undefined) {

src/vs/editor/browser/editorBrowser.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -825,6 +825,8 @@ export interface ICodeEditor extends editorCommon.IEditor {
825825
*/
826826
readonly onEndUpdate: Event<void>;
827827

828+
readonly onDidChangeViewZones: Event<void>;
829+
828830
/**
829831
* Saves current view state of the editor in a serializable object.
830832
*/

src/vs/editor/browser/observableCodeEditor.ts

Lines changed: 35 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55

66
import { equalsIfDefined, itemsEquals } from '../../base/common/equals.js';
77
import { Disposable, DisposableStore, IDisposable, toDisposable } from '../../base/common/lifecycle.js';
8-
import { DebugLocation, IObservable, IObservableWithChange, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableValue, observableValueOpts } from '../../base/common/observable.js';
8+
import { DebugLocation, IObservable, IObservableWithChange, ITransaction, TransactionImpl, autorun, autorunOpts, derived, derivedOpts, derivedWithSetter, observableFromEvent, observableSignal, observableSignalFromEvent, observableValue, observableValueOpts } from '../../base/common/observable.js';
99
import { EditorOption, FindComputedEditorOptionValueById } from '../common/config/editorOptions.js';
1010
import { LineRange } from '../common/core/ranges/lineRange.js';
1111
import { OffsetRange } from '../common/core/ranges/offsetRange.js';
@@ -148,6 +148,9 @@ export class ObservableCodeEditor extends Disposable {
148148
this.layoutInfoVerticalScrollbarWidth = this.layoutInfo.map(l => l.verticalScrollbarWidth);
149149
this.contentWidth = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentWidth());
150150
this.contentHeight = observableFromEvent(this.editor.onDidContentSizeChange, () => this.editor.getContentHeight());
151+
this._onDidChangeViewZones = observableSignalFromEvent(this, this.editor.onDidChangeViewZones);
152+
this._onDidHiddenAreasChanged = observableSignalFromEvent(this, this.editor.onDidChangeHiddenAreas);
153+
this._onDidLineHeightChanged = observableSignalFromEvent(this, this.editor.onDidChangeLineHeight);
151154

152155
this._widgetCounter = 0;
153156
this.openedPeekWidgets = observableValue(this, 0);
@@ -456,6 +459,37 @@ export class ObservableCodeEditor extends Disposable {
456459
});
457460
}
458461

462+
private readonly _onDidChangeViewZones;
463+
private readonly _onDidHiddenAreasChanged;
464+
private readonly _onDidLineHeightChanged;
465+
466+
/**
467+
* Get the vertical position (top offset) for the line's bottom w.r.t. to the first line.
468+
*/
469+
observeTopForLineNumber(lineNumber: number): IObservable<number> {
470+
return derived(reader => {
471+
this.layoutInfo.read(reader);
472+
this._onDidChangeViewZones.read(reader);
473+
this._onDidHiddenAreasChanged.read(reader);
474+
this._onDidLineHeightChanged.read(reader);
475+
this._versionId.read(reader);
476+
return this.editor.getTopForLineNumber(lineNumber);
477+
});
478+
}
479+
480+
/**
481+
* Get the vertical position (top offset) for the line's bottom w.r.t. to the first line.
482+
*/
483+
observeBottomForLineNumber(lineNumber: number): IObservable<number> {
484+
return derived(reader => {
485+
this.layoutInfo.read(reader);
486+
this._onDidChangeViewZones.read(reader);
487+
this._onDidHiddenAreasChanged.read(reader);
488+
this._onDidLineHeightChanged.read(reader);
489+
this._versionId.read(reader);
490+
return this.editor.getBottomForLineNumber(lineNumber);
491+
});
492+
}
459493
}
460494

461495
interface IObservableOverlayWidget {

src/vs/editor/browser/widget/diffEditor/features/hideUnchangedRegionsFeature.ts

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,13 +31,14 @@ import { IObservableViewZone, PlaceholderViewZone, ViewZoneOverlayWidget, applyO
3131
* Make sure to add the view zones to the editor!
3232
*/
3333
export class HideUnchangedRegionsFeature extends Disposable {
34-
private static readonly _breadcrumbsSourceFactory = observableValue<((textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource)>(
34+
public static readonly _breadcrumbsSourceFactory = observableValue<((textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource)>(
3535
this, () => ({
3636
dispose() {
3737
},
3838
getBreadcrumbItems(startRange, reader) {
3939
return [];
4040
},
41+
getAt: () => [],
4142
}));
4243
public static setBreadcrumbsSourceFactory(factory: (textModel: ITextModel, instantiationService: IInstantiationService) => IDiffEditorBreadcrumbsSource) {
4344
this._breadcrumbsSourceFactory.set(factory, undefined);
@@ -491,4 +492,6 @@ class CollapsedCodeOverlayWidget extends ViewZoneOverlayWidget {
491492

492493
export interface IDiffEditorBreadcrumbsSource extends IDisposable {
493494
getBreadcrumbItems(startRange: LineRange, reader: IReader): { name: string; kind: SymbolKind; startLineNumber: number }[];
495+
496+
getAt(lineNumber: number, reader: IReader): { name: string; kind: SymbolKind; startLineNumber: number }[];
494497
}

src/vs/editor/common/config/editorOptions.ts

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4442,6 +4442,8 @@ export interface IInlineSuggestOptions {
44424442

44434443
showCollapsed?: boolean;
44444444

4445+
showLongDistanceHint?: boolean;
4446+
44454447
/**
44464448
* @internal
44474449
*/
@@ -4500,6 +4502,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
45004502
showCollapsed: false,
45014503
renderSideBySide: 'auto',
45024504
allowCodeShifting: 'always',
4505+
showLongDistanceHint: false,
45034506
},
45044507
triggerCommandOnProviderChange: false,
45054508
experimental: {
@@ -4599,6 +4602,12 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
45994602
enum: ['always', 'horizontal', 'never'],
46004603
tags: ['nextEditSuggestions']
46014604
},
4605+
'editor.inlineSuggest.edits.showLongDistanceHint': {
4606+
type: 'boolean',
4607+
default: defaults.edits.showLongDistanceHint,
4608+
description: nls.localize('inlineSuggest.edits.showLongDistanceHint', "Controls whether long distance inline suggestions are shown."),
4609+
tags: ['nextEditSuggestions', 'experimental']
4610+
},
46024611
'editor.inlineSuggest.edits.renderSideBySide': {
46034612
type: 'string',
46044613
default: defaults.edits.renderSideBySide,
@@ -4650,6 +4659,7 @@ class InlineEditorSuggest extends BaseEditorOption<EditorOption.inlineSuggest, I
46504659
enabled: boolean(input.enabled, this.defaultValue.edits.enabled),
46514660
showCollapsed: boolean(input.showCollapsed, this.defaultValue.edits.showCollapsed),
46524661
allowCodeShifting: stringSet(input.allowCodeShifting, this.defaultValue.edits.allowCodeShifting, ['always', 'horizontal', 'never']),
4662+
showLongDistanceHint: boolean(input.showLongDistanceHint, this.defaultValue.edits.showLongDistanceHint),
46534663
renderSideBySide: stringSet(input.renderSideBySide, this.defaultValue.edits.renderSideBySide, ['never', 'auto']),
46544664
};
46554665
}

src/vs/editor/common/core/2d/rect.ts

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
import { BugIndicatingError } from '../../../../base/common/errors.js';
77
import { OffsetRange } from '../ranges/offsetRange.js';
88
import { Point } from './point.js';
9+
import { Size2D } from './size.js';
910

1011
export class Rect {
1112
public static fromPoint(point: Point): Rect {
@@ -204,6 +205,10 @@ export class Rect {
204205
return new Rect(this.left, this.top + delta, this.right, this.bottom + delta);
205206
}
206207

208+
translate(point: Point): Rect {
209+
return new Rect(this.left + point.x, this.top + point.y, this.right + point.x, this.bottom + point.y);
210+
}
211+
207212
deltaRight(delta: number): Rect {
208213
return new Rect(this.left, this.top, this.right + delta, this.bottom);
209214
}
@@ -245,4 +250,24 @@ export class Rect {
245250
height: `${this.height}px`,
246251
};
247252
}
253+
254+
getHorizontalRange(): OffsetRange {
255+
return new OffsetRange(this.left, this.right);
256+
}
257+
258+
getVerticalRange(): OffsetRange {
259+
return new OffsetRange(this.top, this.bottom);
260+
}
261+
262+
withHorizontalRange(range: OffsetRange): Rect {
263+
return new Rect(range.start, this.top, range.endExclusive, this.bottom);
264+
}
265+
266+
withVerticalRange(range: OffsetRange): Rect {
267+
return new Rect(this.left, range.start, this.right, range.endExclusive);
268+
}
269+
270+
getSize(): Size2D {
271+
return new Size2D(this.width, this.height);
272+
}
248273
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { IDimension } from './dimension.js';
7+
8+
export class Size2D {
9+
static equals(a: Size2D, b: Size2D): boolean {
10+
return a.width === b.width && a.height === b.height;
11+
}
12+
13+
constructor(
14+
public readonly width: number,
15+
public readonly height: number,
16+
) { }
17+
18+
public add(other: Size2D): Size2D {
19+
return new Size2D(this.width + other.width, this.height + other.height);
20+
}
21+
22+
public deltaX(delta: number): Size2D {
23+
return new Size2D(this.width + delta, this.height);
24+
}
25+
26+
public deltaY(delta: number): Size2D {
27+
return new Size2D(this.width, this.height + delta);
28+
}
29+
30+
public toString() {
31+
return `(${this.width},${this.height})`;
32+
}
33+
34+
public subtract(other: Size2D): Size2D {
35+
return new Size2D(this.width - other.width, this.height - other.height);
36+
}
37+
38+
public scale(factor: number): Size2D {
39+
return new Size2D(this.width * factor, this.height * factor);
40+
}
41+
42+
public scaleWidth(factor: number): Size2D {
43+
return new Size2D(this.width * factor, this.height);
44+
}
45+
46+
public mapComponents(map: (value: number) => number): Size2D {
47+
return new Size2D(map(this.width), map(this.height));
48+
}
49+
50+
public isZero(): boolean {
51+
return this.width === 0 && this.height === 0;
52+
}
53+
54+
public transpose(): Size2D {
55+
return new Size2D(this.height, this.width);
56+
}
57+
58+
public toDimension(): IDimension {
59+
return { width: this.width, height: this.height };
60+
}
61+
}

src/vs/editor/common/core/ranges/offsetRange.ts

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,10 @@ export class OffsetRange implements IOffsetRange {
208208
}
209209
return new OffsetRange(this.start, range.endExclusive);
210210
}
211+
212+
public withMargin(margin: number): OffsetRange {
213+
return new OffsetRange(this.start - margin, this.endExclusive + margin);
214+
}
211215
}
212216

213217
export class OffsetRangeSet {

src/vs/editor/contrib/diffEditorBreadcrumbs/browser/contribution.ts

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,17 @@ class DiffEditorBreadcrumbsSource extends Disposable implements IDiffEditorBread
5555
symbols.sort(reverseOrder(compareBy(s => s.range.endLineNumber - s.range.startLineNumber, numberComparator)));
5656
return symbols.map(s => ({ name: s.name, kind: s.kind, startLineNumber: s.range.startLineNumber }));
5757
}
58+
59+
public getAt(lineNumber: number, reader: IReader): { name: string; kind: SymbolKind; startLineNumber: number }[] {
60+
const m = this._currentModel.read(reader);
61+
if (!m) { return []; }
62+
const symbols = m.asListOfDocumentSymbols()
63+
.filter(s => new LineRange(s.range.startLineNumber, s.range.endLineNumber).contains(lineNumber));
64+
if (symbols.length === 0) { return []; }
65+
symbols.sort(reverseOrder(compareBy(s => s.range.endLineNumber - s.range.startLineNumber, numberComparator)));
66+
67+
return symbols.map(s => ({ name: s.name, kind: s.kind, startLineNumber: s.range.startLineNumber }));
68+
}
5869
}
5970

6071
HideUnchangedRegionsFeature.setBreadcrumbsSourceFactory((textModel, instantiationService) => {

src/vs/editor/contrib/inlineCompletions/browser/model/inlineCompletionsModel.ts

Lines changed: 15 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -96,6 +96,10 @@ export class InlineCompletionsModel extends Disposable {
9696
private readonly _suppressInSnippetMode;
9797
private readonly _isInSnippetMode;
9898

99+
get editor() {
100+
return this._editor;
101+
}
102+
99103
constructor(
100104
public readonly textModel: ITextModel,
101105
private readonly _selectedSuggestItem: IObservable<SuggestItemInfo | undefined>,
@@ -1206,13 +1210,22 @@ class FadeoutDecoration extends Disposable {
12061210
}
12071211
}
12081212

1209-
function isSuggestionInViewport(editor: ICodeEditor, suggestion: InlineSuggestionItem): boolean {
1213+
export function isSuggestionInViewport(editor: ICodeEditor, suggestion: InlineSuggestionItem, reader: IReader | undefined = undefined): boolean {
12101214
const targetRange = suggestion.targetRange;
1215+
1216+
// TODO make getVisibleRanges reactive!
1217+
observableCodeEditor(editor).scrollTop.read(reader);
12111218
const visibleRanges = editor.getVisibleRanges();
1219+
12121220
if (visibleRanges.length < 1) {
12131221
return false;
12141222
}
12151223

1216-
const viewportRange = new Range(visibleRanges[0].startLineNumber, visibleRanges[0].startColumn, visibleRanges[visibleRanges.length - 1].endLineNumber, visibleRanges[visibleRanges.length - 1].endColumn);
1224+
const viewportRange = new Range(
1225+
visibleRanges[0].startLineNumber,
1226+
visibleRanges[0].startColumn,
1227+
visibleRanges[visibleRanges.length - 1].endLineNumber,
1228+
visibleRanges[visibleRanges.length - 1].endColumn
1229+
);
12171230
return viewportRange.containsRange(targetRange);
12181231
}

0 commit comments

Comments
 (0)