2
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
4
*--------------------------------------------------------------------------------------------*/
5
- import { Disposable , DisposableStore } from 'vs/base/common/lifecycle' ;
6
- import { ICodeEditor , IOverlayWidget , IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser' ;
5
+ import { Disposable , DisposableStore , IDisposable , toDisposable } from 'vs/base/common/lifecycle' ;
6
+ import { IActiveCodeEditor , ICodeEditor , IOverlayWidget , IOverlayWidgetPosition } from 'vs/editor/browser/editorBrowser' ;
7
7
import * as dom from 'vs/base/browser/dom' ;
8
8
import { EditorLayoutInfo , EditorOption , RenderLineNumbersType } from 'vs/editor/common/config/editorOptions' ;
9
9
import { createStringBuilder } from 'vs/editor/common/core/stringBuilder' ;
10
10
import { RenderLineInput , renderViewLine } from 'vs/editor/common/viewLayout/viewLineRenderer' ;
11
11
import { LineDecoration } from 'vs/editor/common/viewLayout/lineDecorations' ;
12
12
import { Position } from 'vs/editor/common/core/position' ;
13
+ import { ClickLinkGesture } from 'vs/editor/contrib/gotoSymbol/browser/link/clickLinkGesture' ;
14
+ import { getDefinitionsAtPosition } from 'vs/editor/contrib/gotoSymbol/browser/goToSymbol' ;
15
+ import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures' ;
16
+ import { Location } from 'vs/editor/common/languages' ;
17
+ import { goToDefinitionWithLocation } from 'vs/editor/contrib/inlayHints/browser/inlayHintsLocations' ;
18
+ import { IInstantiationService } from 'vs/platform/instantiation/common/instantiation' ;
19
+ import { CancellationTokenSource } from 'vs/base/common/cancellation' ;
20
+ import { IRange , Range } from 'vs/editor/common/core/range' ;
21
+ import { StandardMouseEvent } from 'vs/base/browser/mouseEvent' ;
13
22
import 'vs/css!./stickyScroll' ;
14
23
24
+ interface CustomMouseEvent {
25
+ detail : string ;
26
+ element : HTMLElement ;
27
+ }
28
+
15
29
export class StickyScrollWidgetState {
16
30
constructor (
17
31
public readonly lineNumbers : number [ ] ,
@@ -29,24 +43,115 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
29
43
private _lineHeight : number ;
30
44
private _lineNumbers : number [ ] ;
31
45
private _lastLineRelativePosition : number ;
46
+ private _hoverOnLine : number ;
47
+ private _hoverOnColumn : number ;
48
+ private _stickyRangeProjectedOnEditor : IRange | null ;
32
49
33
- constructor ( private readonly editor : ICodeEditor ) {
50
+ constructor (
51
+ private readonly _editor : ICodeEditor ,
52
+ @ILanguageFeaturesService private readonly _languageFeatureService : ILanguageFeaturesService ,
53
+ @IInstantiationService private readonly _instaService : IInstantiationService
54
+ ) {
34
55
super ( ) ;
35
- this . _layoutInfo = this . editor . getLayoutInfo ( ) ;
56
+ this . _layoutInfo = this . _editor . getLayoutInfo ( ) ;
36
57
this . _rootDomNode = document . createElement ( 'div' ) ;
37
58
this . _rootDomNode . className = 'sticky-widget' ;
38
59
this . _rootDomNode . style . width = `${ this . _layoutInfo . width - this . _layoutInfo . minimap . minimapCanvasOuterWidth - this . _layoutInfo . verticalScrollbarWidth } px` ;
39
-
40
60
this . _lineNumbers = [ ] ;
41
61
this . _lastLineRelativePosition = 0 ;
42
-
43
- this . _lineHeight = this . editor . getOption ( EditorOption . lineHeight ) ;
44
- this . _register ( this . editor . onDidChangeConfiguration ( e => {
62
+ this . _hoverOnLine = - 1 ;
63
+ this . _hoverOnColumn = - 1 ;
64
+ this . _stickyRangeProjectedOnEditor = null ;
65
+ this . _lineHeight = this . _editor . getOption ( EditorOption . lineHeight ) ;
66
+ this . _register ( this . _editor . onDidChangeConfiguration ( e => {
45
67
if ( e . hasChanged ( EditorOption . lineHeight ) ) {
46
- this . _lineHeight = this . editor . getOption ( EditorOption . lineHeight ) ;
68
+ this . _lineHeight = this . _editor . getOption ( EditorOption . lineHeight ) ;
69
+ }
70
+
71
+ } ) ) ;
72
+ this . _register ( this . updateLinkGesture ( ) ) ;
73
+ }
74
+
75
+ private updateLinkGesture ( ) : IDisposable {
76
+
77
+
78
+ const linkGestureStore = new DisposableStore ( ) ;
79
+ const sessionStore = new DisposableStore ( ) ;
80
+ linkGestureStore . add ( sessionStore ) ;
81
+ const gesture = new ClickLinkGesture ( this . _editor , true ) ;
82
+ linkGestureStore . add ( gesture ) ;
83
+
84
+ linkGestureStore . add ( gesture . onMouseMoveOrRelevantKeyDown ( ( [ mouseEvent , _keyboardEvent ] ) => {
85
+ if ( ! this . _editor . hasModel ( ) || ! mouseEvent . hasTriggerModifier ) {
86
+ sessionStore . clear ( ) ;
87
+ return ;
47
88
}
89
+ const targetMouseEvent = mouseEvent . target as unknown as CustomMouseEvent ;
90
+ if ( targetMouseEvent . detail === this . getId ( ) && targetMouseEvent . element . innerText === targetMouseEvent . element . innerHTML ) {
91
+ const text = targetMouseEvent . element . innerText ;
92
+ if ( this . _hoverOnColumn === - 1 ) {
93
+ return ;
94
+ }
95
+ const lineNumber = this . _hoverOnLine ;
96
+ const column = this . _hoverOnColumn ;
97
+
98
+ const stickyPositionProjectedOnEditor = new Range ( lineNumber , column , lineNumber , column + text . length ) ;
99
+ if ( ! stickyPositionProjectedOnEditor . equalsRange ( this . _stickyRangeProjectedOnEditor ) ) {
100
+ this . _stickyRangeProjectedOnEditor = stickyPositionProjectedOnEditor ;
101
+ sessionStore . clear ( ) ;
102
+ } else if ( targetMouseEvent . element . style . textDecoration === 'underline' ) {
103
+ return ;
104
+ }
105
+
106
+ const cancellationToken = new CancellationTokenSource ( ) ;
107
+ sessionStore . add ( toDisposable ( ( ) => cancellationToken . dispose ( true ) ) ) ;
108
+
109
+ let currentHTMLChild : HTMLElement ;
110
+
111
+ getDefinitionsAtPosition ( this . _languageFeatureService . definitionProvider , this . _editor . getModel ( ) , new Position ( lineNumber , column + 1 ) , cancellationToken . token ) . then ( ( candidateDefinitions => {
112
+ if ( cancellationToken . token . isCancellationRequested ) {
113
+ return ;
114
+ }
115
+ if ( candidateDefinitions . length !== 0 ) {
116
+ const childHTML : HTMLElement = targetMouseEvent . element ;
117
+ if ( currentHTMLChild !== childHTML ) {
118
+ sessionStore . clear ( ) ;
119
+ currentHTMLChild = childHTML ;
120
+ currentHTMLChild . style . textDecoration = 'underline' ;
121
+ sessionStore . add ( toDisposable ( ( ) => {
122
+ currentHTMLChild . style . textDecoration = 'none' ;
123
+ } ) ) ;
124
+ } else if ( ! currentHTMLChild ) {
125
+ currentHTMLChild = childHTML ;
126
+ currentHTMLChild . style . textDecoration = 'underline' ;
127
+ sessionStore . add ( toDisposable ( ( ) => {
128
+ currentHTMLChild . style . textDecoration = 'none' ;
129
+ } ) ) ;
130
+ }
131
+ } else {
132
+ sessionStore . clear ( ) ;
133
+ }
134
+ } ) ) ;
135
+ } else {
136
+ sessionStore . clear ( ) ;
137
+ }
138
+ } ) ) ;
139
+ linkGestureStore . add ( gesture . onCancel ( ( ) => {
140
+ sessionStore . clear ( ) ;
48
141
} ) ) ;
142
+ linkGestureStore . add ( gesture . onExecute ( async e => {
143
+ if ( this . _hoverOnLine !== - 1 ) {
144
+ if ( e . hasTriggerModifier ) {
145
+ // Control click
146
+ this . _instaService . invokeFunction ( goToDefinitionWithLocation , e , this . _editor as IActiveCodeEditor , { uri : this . _editor . getModel ( ) ! . uri , range : this . _stickyRangeProjectedOnEditor } as Location ) ;
147
+ } else {
148
+ // Normal click
149
+ this . _editor . revealPosition ( { lineNumber : this . _hoverOnLine , column : 1 } ) ;
150
+ }
151
+ }
49
152
153
+ } ) ) ;
154
+ return linkGestureStore ;
50
155
}
51
156
52
157
@@ -75,14 +180,14 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
75
180
private getChildNode ( index : number , line : number ) : HTMLElement {
76
181
77
182
const child = document . createElement ( 'div' ) ;
78
- const viewModel = this . editor . _getViewModel ( ) ;
183
+ const viewModel = this . _editor . _getViewModel ( ) ;
79
184
const viewLineNumber = viewModel ! . coordinatesConverter . convertModelPositionToViewPosition ( new Position ( line , 1 ) ) . lineNumber ;
80
185
const lineRenderingData = viewModel ! . getViewLineRenderingData ( viewLineNumber ) ;
81
- const layoutInfo = this . editor . getLayoutInfo ( ) ;
186
+ const layoutInfo = this . _editor . getLayoutInfo ( ) ;
82
187
const width = layoutInfo . width - layoutInfo . minimap . minimapCanvasOuterWidth - layoutInfo . verticalScrollbarWidth ;
83
- const minimapSide = this . editor . getOption ( EditorOption . minimap ) . side ;
84
- const lineHeight = this . editor . getOption ( EditorOption . lineHeight ) ;
85
- const lineNumberOption = this . editor . getOption ( EditorOption . lineNumbers ) ;
188
+ const minimapSide = this . _editor . getOption ( EditorOption . minimap ) . side ;
189
+ const lineHeight = this . _editor . getOption ( EditorOption . lineHeight ) ;
190
+ const lineNumberOption = this . _editor . getOption ( EditorOption . lineNumbers ) ;
86
191
87
192
let actualInlineDecorations : LineDecoration [ ] ;
88
193
try {
@@ -112,6 +217,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
112
217
113
218
const lineHTMLNode = document . createElement ( 'span' ) ;
114
219
lineHTMLNode . className = 'sticky-line' ;
220
+ lineHTMLNode . classList . add ( `stickyLine${ line } ` ) ;
115
221
lineHTMLNode . style . lineHeight = `${ lineHeight } px` ;
116
222
lineHTMLNode . innerHTML = newLine as string ;
117
223
@@ -128,7 +234,7 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
128
234
if ( lineNumberOption . renderType === RenderLineNumbersType . On || lineNumberOption . renderType === RenderLineNumbersType . Interval && line % 10 === 0 ) {
129
235
innerLineNumberHTML . innerText = line . toString ( ) ;
130
236
} else if ( lineNumberOption . renderType === RenderLineNumbersType . Relative ) {
131
- innerLineNumberHTML . innerText = Math . abs ( line - this . editor . getPosition ( ) ! . lineNumber ) . toString ( ) ;
237
+ innerLineNumberHTML . innerText = Math . abs ( line - this . _editor . getPosition ( ) ! . lineNumber ) . toString ( ) ;
132
238
}
133
239
innerLineNumberHTML . className = 'sticky-line-number' ;
134
240
innerLineNumberHTML . style . lineHeight = `${ lineHeight } px` ;
@@ -140,8 +246,8 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
140
246
}
141
247
lineNumberHTMLNode . appendChild ( innerLineNumberHTML ) ;
142
248
143
- this . editor . applyFontInfo ( lineHTMLNode ) ;
144
- this . editor . applyFontInfo ( innerLineNumberHTML ) ;
249
+ this . _editor . applyFontInfo ( lineHTMLNode ) ;
250
+ this . _editor . applyFontInfo ( innerLineNumberHTML ) ;
145
251
146
252
child . appendChild ( lineNumberHTMLNode ) ;
147
253
child . appendChild ( lineHTMLNode ) ;
@@ -158,18 +264,22 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
158
264
child . style . zIndex = '-1' ;
159
265
child . style . top = this . _lastLineRelativePosition + 'px' ;
160
266
}
161
- this . _disposableStore . add ( dom . addDisposableListener ( child , 'click' , e => {
162
- e . stopPropagation ( ) ;
163
- e . preventDefault ( ) ;
164
- this . editor . revealPosition ( { lineNumber : line - index , column : 1 } ) ;
267
+
268
+ this . _disposableStore . add ( dom . addDisposableListener ( child , 'mouseover' , ( e ) => {
269
+ if ( this . _editor . hasModel ( ) ) {
270
+ const mouseOverEvent = new StandardMouseEvent ( e ) ;
271
+ const text = mouseOverEvent . target . innerText ;
272
+ this . _hoverOnLine = line ;
273
+ // TODO: workaround to find the column index, perhaps need more solid solution
274
+ this . _hoverOnColumn = this . _editor . getModel ( ) . getLineContent ( line ) . indexOf ( text ) + 1 || - 1 ;
275
+ }
165
276
} ) ) ;
166
277
167
278
return child ;
168
279
}
169
280
170
281
private renderRootNode ( ) : void {
171
-
172
- if ( ! this . editor . _getViewModel ( ) ) {
282
+ if ( ! this . _editor . _getViewModel ( ) ) {
173
283
return ;
174
284
}
175
285
for ( const [ index , line ] of this . _lineNumbers . entries ( ) ) {
@@ -178,9 +288,9 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
178
288
179
289
const widgetHeight : number = this . _lineNumbers . length * this . _lineHeight + this . _lastLineRelativePosition ;
180
290
this . _rootDomNode . style . height = widgetHeight . toString ( ) + 'px' ;
181
- const minimapSide = this . editor . getOption ( EditorOption . minimap ) . side ;
291
+ const minimapSide = this . _editor . getOption ( EditorOption . minimap ) . side ;
182
292
if ( minimapSide === 'left' ) {
183
- this . _rootDomNode . style . marginLeft = this . editor . getLayoutInfo ( ) . minimap . minimapCanvasOuterWidth + 'px' ;
293
+ this . _rootDomNode . style . marginLeft = this . _editor . getLayoutInfo ( ) . minimap . minimapCanvasOuterWidth + 'px' ;
184
294
} else if ( minimapSide === 'right' ) {
185
295
this . _rootDomNode . style . marginLeft = '0px' ;
186
296
}
@@ -203,6 +313,5 @@ export class StickyScrollWidget extends Disposable implements IOverlayWidget {
203
313
204
314
override dispose ( ) : void {
205
315
super . dispose ( ) ;
206
- this . _disposableStore . dispose ( ) ;
207
316
}
208
317
}
0 commit comments