Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/inlineChat/browser/inlineChatAffordance.ts
5241 views
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 } from '../../../../base/common/lifecycle.js';
7
import { autorun, debouncedObservable, derived, observableSignalFromEvent, observableValue, runOnChange, waitForState } from '../../../../base/common/observable.js';
8
import { ICodeEditor } from '../../../../editor/browser/editorBrowser.js';
9
import { observableCodeEditor } from '../../../../editor/browser/observableCodeEditor.js';
10
import { ScrollType } from '../../../../editor/common/editorCommon.js';
11
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
12
import { InlineChatConfigKeys } from '../common/inlineChat.js';
13
import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js';
14
import { observableConfigValue } from '../../../../platform/observable/common/platformObservableUtils.js';
15
import { IChatEntitlementService } from '../../../services/chat/common/chatEntitlementService.js';
16
import { InlineChatEditorAffordance } from './inlineChatEditorAffordance.js';
17
import { InlineChatInputWidget } from './inlineChatOverlayWidget.js';
18
import { InlineChatGutterAffordance } from './inlineChatGutterAffordance.js';
19
import { Selection, SelectionDirection } from '../../../../editor/common/core/selection.js';
20
import { assertType } from '../../../../base/common/types.js';
21
import { CursorChangeReason } from '../../../../editor/common/cursorEvents.js';
22
import { IInlineChatSessionService } from './inlineChatSessionService.js';
23
24
export class InlineChatAffordance extends Disposable {
25
26
private _menuData = observableValue<{ rect: DOMRect; above: boolean; lineNumber: number } | undefined>(this, undefined);
27
28
constructor(
29
private readonly _editor: ICodeEditor,
30
private readonly _inputWidget: InlineChatInputWidget,
31
@IInstantiationService private readonly _instantiationService: IInstantiationService,
32
@IConfigurationService configurationService: IConfigurationService,
33
@IChatEntitlementService chatEntiteldService: IChatEntitlementService,
34
@IInlineChatSessionService inlineChatSessionService: IInlineChatSessionService,
35
) {
36
super();
37
38
const editorObs = observableCodeEditor(this._editor);
39
const affordance = observableConfigValue<'off' | 'gutter' | 'editor'>(InlineChatConfigKeys.Affordance, 'off', configurationService);
40
const debouncedSelection = debouncedObservable(editorObs.cursorSelection, 500);
41
42
const selectionData = observableValue<Selection | undefined>(this, undefined);
43
44
let explicitSelection = false;
45
46
this._store.add(runOnChange(editorObs.selections, (value, _prev, events) => {
47
explicitSelection = events.every(e => e.reason === CursorChangeReason.Explicit);
48
if (!value || value.length !== 1 || value[0].isEmpty() || !explicitSelection) {
49
selectionData.set(undefined, undefined);
50
}
51
}));
52
53
this._store.add(autorun(r => {
54
const value = debouncedSelection.read(r);
55
if (!value || value.isEmpty() || !explicitSelection || _editor.getModel()?.getValueInRange(value).match(/^\s+$/)) {
56
selectionData.set(undefined, undefined);
57
return;
58
}
59
selectionData.set(value, undefined);
60
}));
61
62
this._store.add(autorun(r => {
63
if (chatEntiteldService.sentimentObs.read(r).hidden) {
64
selectionData.set(undefined, undefined);
65
}
66
}));
67
68
const hasSessionObs = derived(r => {
69
observableSignalFromEvent(this, inlineChatSessionService.onDidChangeSessions).read(r);
70
const model = editorObs.model.read(r);
71
return model ? inlineChatSessionService.getSessionByTextModel(model.uri) !== undefined : false;
72
});
73
74
this._store.add(autorun(r => {
75
if (hasSessionObs.read(r)) {
76
selectionData.set(undefined, undefined);
77
}
78
}));
79
80
this._store.add(this._instantiationService.createInstance(
81
InlineChatGutterAffordance,
82
editorObs,
83
derived(r => affordance.read(r) === 'gutter' ? selectionData.read(r) : undefined),
84
this._menuData
85
));
86
87
this._store.add(this._instantiationService.createInstance(
88
InlineChatEditorAffordance,
89
this._editor,
90
derived(r => affordance.read(r) === 'editor' ? selectionData.read(r) : undefined)
91
));
92
93
this._store.add(autorun(r => {
94
const data = this._menuData.read(r);
95
if (!data) {
96
return;
97
}
98
99
// Reveal the line in case it's outside the viewport (e.g., when triggered from sticky scroll)
100
this._editor.revealLineInCenterIfOutsideViewport(data.lineNumber, ScrollType.Immediate);
101
102
const editorDomNode = this._editor.getDomNode()!;
103
const editorRect = editorDomNode.getBoundingClientRect();
104
const left = data.rect.left - editorRect.left;
105
106
// Show the overlay widget
107
this._inputWidget.show(data.lineNumber, left, data.above);
108
}));
109
110
this._store.add(autorun(r => {
111
const pos = this._inputWidget.position.read(r);
112
if (pos === null) {
113
this._menuData.set(undefined, undefined);
114
}
115
}));
116
}
117
118
async showMenuAtSelection() {
119
assertType(this._editor.hasModel());
120
121
const direction = this._editor.getSelection().getDirection();
122
const position = this._editor.getPosition();
123
const editorDomNode = this._editor.getDomNode();
124
const scrolledPosition = this._editor.getScrolledVisiblePosition(position);
125
const editorRect = editorDomNode.getBoundingClientRect();
126
const x = editorRect.left + scrolledPosition.left;
127
const y = editorRect.top + scrolledPosition.top;
128
129
this._menuData.set({
130
rect: new DOMRect(x, y, 0, scrolledPosition.height),
131
above: direction === SelectionDirection.RTL,
132
lineNumber: position.lineNumber
133
}, undefined);
134
135
await waitForState(this._inputWidget.position, pos => pos === null);
136
}
137
}
138
139