Skip to content

Commit 5d634d5

Browse files
author
aiday-mar
committed
work in progress
1 parent c3470b5 commit 5d634d5

File tree

3 files changed

+257
-1
lines changed

3 files changed

+257
-1
lines changed
Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,82 @@
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 { Emitter, Event } from 'vs/base/common/event';
7+
import { localize } from 'vs/nls';
8+
import { IConfigurationOverrides, IConfigurationService } from 'vs/platform/configuration/common/configuration';
9+
import { Extensions, IConfigurationRegistry } from 'vs/platform/configuration/common/configurationRegistry';
10+
import { Registry } from 'vs/platform/registry/common/platform';
11+
12+
export abstract class StickyScrollConfig<T> {
13+
14+
abstract get name(): string;
15+
abstract get onDidChange(): Event<void>;
16+
17+
abstract getValue(overrides?: IConfigurationOverrides): T;
18+
abstract updateValue(value: T, overrides?: IConfigurationOverrides): Promise<void>;
19+
abstract dispose(): void;
20+
21+
private constructor() { }
22+
23+
static readonly IsEnabled = StickyScrollConfig._stub<boolean>('editor.experimental.stickyScroll.enabled');
24+
25+
private static _stub<T>(name: string): { bindTo(service: IConfigurationService): StickyScrollConfig<T> } {
26+
return {
27+
bindTo(service) {
28+
const onDidChange = new Emitter<void>();
29+
30+
const listener = service.onDidChangeConfiguration(e => {
31+
if (e.affectsConfiguration(name)) {
32+
onDidChange.fire(undefined);
33+
}
34+
});
35+
36+
return new class implements StickyScrollConfig<T>{
37+
readonly name = name;
38+
readonly onDidChange = onDidChange.event;
39+
getValue(overrides?: IConfigurationOverrides): T {
40+
if (overrides) {
41+
return service.getValue(name, overrides);
42+
} else {
43+
return service.getValue(name);
44+
}
45+
}
46+
updateValue(newValue: T, overrides?: IConfigurationOverrides): Promise<void> {
47+
if (overrides) {
48+
return service.updateValue(name, newValue, overrides);
49+
} else {
50+
return service.updateValue(name, newValue);
51+
}
52+
}
53+
dispose(): void {
54+
listener.dispose();
55+
onDidChange.dispose();
56+
}
57+
};
58+
}
59+
};
60+
}
61+
}
62+
63+
Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerConfiguration({
64+
id: 'stickyScroll',
65+
title: localize('title', "Sticky Scroll"),
66+
order: 101,
67+
type: 'object',
68+
properties: {
69+
'editor.experimental.stickyScroll.enabled': {
70+
description: localize('editor.experimental.stickyScroll', "Shows the nested current scopes during the scroll at the top of the editor."),
71+
type: 'boolean',
72+
default: false
73+
},
74+
'editor.experimental.stickyScroll.maxLineCount': {
75+
description: localize('editor.experimental.stickyScroll.maxLineCount', "Defines the maximum number of sticky lines to show."),
76+
type: 'number',
77+
default: 5,
78+
minimum: 1,
79+
maximum: 10
80+
}
81+
}
82+
});
Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
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 { Disposable, DisposableStore } from 'vs/base/common/lifecycle';
7+
import { ICodeEditor } from 'vs/editor/browser/editorBrowser';
8+
import { registerEditorContribution } from 'vs/editor/browser/editorExtensions';
9+
import { IEditorContribution } from 'vs/editor/common/editorCommon';
10+
import { ILanguageFeaturesService } from 'vs/editor/common/services/languageFeatures';
11+
import { EditorOption, RenderLineNumbersType } from 'vs/editor/common/config/editorOptions';
12+
import { StickyScrollWidget, StickyScrollWidgetState } from './stickyScrollWidget';
13+
import { StickyLineCandidateProvider, StickyRange } from './stickyScrollProvider';
14+
import { IModelTokensChangedEvent } from 'vs/editor/common/textModelEvents';
15+
import { Action2, MenuId, registerAction2 } from 'vs/platform/actions/common/actions';
16+
import { localize } from 'vs/nls';
17+
import { ContextKeyExpr } from 'vs/platform/contextkey/common/contextkey';
18+
import { ServicesAccessor } from 'vs/platform/instantiation/common/instantiation';
19+
import { IConfigurationService } from 'vs/platform/configuration/common/configuration';
20+
import { StickyScrollConfig } from './stickyScrollConfig';
21+
22+
class StickyScrollController extends Disposable implements IEditorContribution {
23+
24+
static readonly ID = 'store.contrib.stickyScrollController';
25+
private readonly editor: ICodeEditor;
26+
private readonly stickyScrollWidget: StickyScrollWidget;
27+
private readonly stickyLineCandidateProvider: StickyLineCandidateProvider;
28+
private readonly sessionStore: DisposableStore = new DisposableStore();
29+
30+
constructor(
31+
editor: ICodeEditor,
32+
@ILanguageFeaturesService _languageFeaturesService: ILanguageFeaturesService,
33+
) {
34+
super();
35+
this.editor = editor;
36+
this.stickyScrollWidget = new StickyScrollWidget(this.editor);
37+
this.stickyLineCandidateProvider = new StickyLineCandidateProvider(this.editor, _languageFeaturesService);
38+
39+
this._register(this.editor.onDidChangeConfiguration(e => {
40+
if (e.hasChanged(EditorOption.experimental)) {
41+
this.readConfiguration();
42+
}
43+
}));
44+
this.readConfiguration();
45+
}
46+
47+
private readConfiguration() {
48+
const options = this.editor.getOption(EditorOption.experimental);
49+
if (options.stickyScroll.enabled === false) {
50+
this.editor.removeOverlayWidget(this.stickyScrollWidget);
51+
this.sessionStore.clear();
52+
return;
53+
} else {
54+
this.editor.addOverlayWidget(this.stickyScrollWidget);
55+
this.sessionStore.add(this.editor.onDidScrollChange(() => this.renderStickyScroll()));
56+
this.sessionStore.add(this.editor.onDidLayoutChange(() => this.onDidResize()));
57+
this.sessionStore.add(this.editor.onDidChangeModelTokens((e) => this.onTokensChange(e)));
58+
this.sessionStore.add(this.stickyLineCandidateProvider.onStickyScrollChange(() => this.renderStickyScroll()));
59+
const lineNumberOption = this.editor.getOption(EditorOption.lineNumbers);
60+
if (lineNumberOption.renderType === RenderLineNumbersType.Relative) {
61+
this.sessionStore.add(this.editor.onDidChangeCursorPosition(() => this.renderStickyScroll()));
62+
}
63+
}
64+
}
65+
66+
private needsUpdate(event: IModelTokensChangedEvent) {
67+
const stickyLineNumbers = this.stickyScrollWidget.getCurrentLines();
68+
for (const stickyLineNumber of stickyLineNumbers) {
69+
for (const range of event.ranges) {
70+
if (stickyLineNumber >= range.fromLineNumber && stickyLineNumber <= range.toLineNumber) {
71+
return true;
72+
}
73+
}
74+
}
75+
return false;
76+
}
77+
78+
private onTokensChange(event: IModelTokensChangedEvent) {
79+
if (this.needsUpdate(event)) {
80+
this.renderStickyScroll();
81+
}
82+
}
83+
84+
private onDidResize() {
85+
const width = this.editor.getLayoutInfo().width - this.editor.getLayoutInfo().minimap.minimapCanvasOuterWidth - this.editor.getLayoutInfo().verticalScrollbarWidth;
86+
this.stickyScrollWidget.getDomNode().style.width = `${width}px`;
87+
}
88+
89+
private renderStickyScroll() {
90+
if (!(this.editor.hasModel())) {
91+
return;
92+
}
93+
const model = this.editor.getModel();
94+
if (this.stickyLineCandidateProvider.getVersionId() !== model.getVersionId()) {
95+
// Old _ranges not updated yet
96+
return;
97+
}
98+
this.stickyScrollWidget.setState(this.getScrollWidgetState());
99+
}
100+
101+
private getScrollWidgetState(): StickyScrollWidgetState {
102+
const lineHeight: number = this.editor.getOption(EditorOption.lineHeight);
103+
const maxNumberStickyLines = this.editor.getOption(EditorOption.experimental).stickyScroll.maxLineCount;
104+
const scrollTop: number = this.editor.getScrollTop();
105+
let lastLineRelativePosition: number = 0;
106+
const lineNumbers: number[] = [];
107+
const arrayVisibleRanges = this.editor.getVisibleRanges();
108+
if (arrayVisibleRanges.length !== 0) {
109+
const fullVisibleRange = new StickyRange(arrayVisibleRanges[0].startLineNumber, arrayVisibleRanges[arrayVisibleRanges.length - 1].endLineNumber);
110+
const candidateRanges = this.stickyLineCandidateProvider.getCandidateStickyLinesIntersecting(fullVisibleRange);
111+
for (const range of candidateRanges) {
112+
const start = range.startLineNumber;
113+
const end = range.endLineNumber;
114+
const depth = range.nestingDepth;
115+
if (end - start > 0) {
116+
const topOfElementAtDepth = (depth - 1) * lineHeight;
117+
const bottomOfElementAtDepth = depth * lineHeight;
118+
119+
const bottomOfBeginningLine = this.editor.getBottomForLineNumber(start) - scrollTop;
120+
const topOfEndLine = this.editor.getTopForLineNumber(end) - scrollTop;
121+
const bottomOfEndLine = this.editor.getBottomForLineNumber(end) - scrollTop;
122+
123+
if (topOfElementAtDepth > topOfEndLine && topOfElementAtDepth <= bottomOfEndLine) {
124+
lineNumbers.push(start);
125+
lastLineRelativePosition = bottomOfEndLine - bottomOfElementAtDepth;
126+
break;
127+
}
128+
else if (bottomOfElementAtDepth > bottomOfBeginningLine && bottomOfElementAtDepth <= bottomOfEndLine) {
129+
lineNumbers.push(start);
130+
}
131+
if (lineNumbers.length === maxNumberStickyLines) {
132+
break;
133+
}
134+
}
135+
}
136+
}
137+
return new StickyScrollWidgetState(lineNumbers, lastLineRelativePosition);
138+
}
139+
140+
override dispose(): void {
141+
super.dispose();
142+
this.sessionStore.dispose();
143+
}
144+
}
145+
146+
registerEditorContribution(StickyScrollController.ID, StickyScrollController);
147+
148+
registerAction2(class ToggleStickyScroll extends Action2 {
149+
150+
constructor() {
151+
super({
152+
id: 'stickyScroll.toggle',
153+
title: {
154+
value: localize('cmd.toggle', "Toggle Sticky Scroll"),
155+
mnemonicTitle: localize('miStickyScroll', "&&Sticky Scroll"),
156+
original: 'Toggle Sticky Scroll',
157+
},
158+
// Hardcoding due to import violation
159+
category: { value: localize('view', "View"), original: 'View' },
160+
toggled: ContextKeyExpr.equals('config.stickyScroll.enabled', true),
161+
menu: [
162+
{ id: MenuId.CommandPalette },
163+
{ id: MenuId.MenubarViewMenu, group: '5_editor', order: 6 },
164+
]
165+
});
166+
}
167+
168+
run(accessor: ServicesAccessor): void {
169+
const config = accessor.get(IConfigurationService);
170+
const value = StickyScrollConfig.IsEnabled.bindTo(config).getValue();
171+
StickyScrollConfig.IsEnabled.bindTo(config).updateValue(!value);
172+
}
173+
});
174+

src/vs/editor/editor.all.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ import 'vs/editor/contrib/links/browser/links';
4141
import 'vs/editor/contrib/multicursor/browser/multicursor';
4242
import 'vs/editor/contrib/parameterHints/browser/parameterHints';
4343
import 'vs/editor/contrib/rename/browser/rename';
44-
import 'vs/editor/contrib/stickyScroll/browser/stickyScroll';
44+
import 'vs/editor/contrib/stickyScroll/browser/stickyScrollController';
4545
import 'vs/editor/contrib/smartSelect/browser/smartSelect';
4646
import 'vs/editor/contrib/snippet/browser/snippetController2';
4747
import 'vs/editor/contrib/suggest/browser/suggestController';

0 commit comments

Comments
 (0)