-
Notifications
You must be signed in to change notification settings - Fork 40.3k
inline chat input: clamp widget to viewport when scrolling with content #291390
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -51,15 +51,15 @@ export class InlineChatInputWidget extends Disposable { | |
| private readonly _input: IActiveCodeEditor; | ||
| private readonly _position = observableValue<IOverlayWidgetPosition | null>(this, null); | ||
| readonly position: IObservable<IOverlayWidgetPosition | null> = this._position; | ||
| readonly minContentWidthInPx = constObservable(0); | ||
|
|
||
|
|
||
| private readonly _showStore = this._store.add(new DisposableStore()); | ||
| private readonly _stickyScrollHeight: IObservable<number>; | ||
| private _inlineStartAction: IAction | undefined; | ||
| private _anchorLineNumber: number = 0; | ||
| private _anchorLeft: number = 0; | ||
| private _anchorAbove: boolean = false; | ||
|
|
||
| readonly allowEditorOverflow = true; | ||
|
|
||
| constructor( | ||
| private readonly _editorObs: ObservableCodeEditor, | ||
|
|
@@ -108,6 +108,10 @@ export class InlineChatInputWidget extends Disposable { | |
| this._input.setModel(model); | ||
| this._input.layout({ width: 200, height: 18 }); | ||
|
|
||
| // Initialize sticky scroll height observable | ||
| const stickyScrollController = StickyScrollController.get(this._editorObs.editor); | ||
| this._stickyScrollHeight = stickyScrollController ? observableFromEvent(stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight) : constObservable(0); | ||
|
|
||
| // Update placeholder based on selection state | ||
| this._store.add(autorun(r => { | ||
| const selection = this._editorObs.cursorSelection.read(r); | ||
|
|
@@ -216,22 +220,23 @@ export class InlineChatInputWidget extends Disposable { | |
| this._showStore.add(this._editorObs.createOverlayWidget({ | ||
| domNode: this._domNode, | ||
| position: this._position, | ||
| minContentWidthInPx: this.minContentWidthInPx, | ||
| allowEditorOverflow: this.allowEditorOverflow, | ||
| minContentWidthInPx: constObservable(0), | ||
| allowEditorOverflow: true, | ||
| })); | ||
|
|
||
| // If anchoring above, adjust position after render to account for widget height | ||
| if (anchorAbove) { | ||
| this._updatePosition(); | ||
| } | ||
|
|
||
| // Update position on scroll, hide if anchor line is out of view | ||
| // Update position on scroll, hide if anchor line is out of view (only when input is empty) | ||
| this._showStore.add(this._editorObs.editor.onDidScrollChange(() => { | ||
| const visibleRanges = this._editorObs.editor.getVisibleRanges(); | ||
| const isLineVisible = visibleRanges.some(range => | ||
| this._anchorLineNumber >= range.startLineNumber && this._anchorLineNumber <= range.endLineNumber | ||
| ); | ||
| if (!isLineVisible) { | ||
| const hasContent = !!this._input.getModel().getValue(); | ||
| if (!isLineVisible && !hasContent) { | ||
| this._hide(); | ||
| } else { | ||
| this._updatePosition(); | ||
|
|
@@ -244,19 +249,30 @@ export class InlineChatInputWidget extends Disposable { | |
|
|
||
| private _updatePosition(): void { | ||
| const editor = this._editorObs.editor; | ||
| const lineHeight = editor.getOption(EditorOption.lineHeight); | ||
| const top = editor.getTopForLineNumber(this._anchorLineNumber) - editor.getScrollTop(); | ||
| let adjustedTop = top; | ||
|
|
||
| if (this._anchorAbove) { | ||
| const widgetHeight = this._domNode.offsetHeight; | ||
| adjustedTop = top - widgetHeight; | ||
| } else { | ||
| const lineHeight = editor.getOption(EditorOption.lineHeight); | ||
| adjustedTop = top + lineHeight; | ||
| } | ||
|
|
||
| // Clamp to viewport bounds when anchor line is out of view | ||
| const stickyScrollHeight = this._stickyScrollHeight.get(); | ||
| const layoutInfo = editor.getLayoutInfo(); | ||
| const widgetHeight = this._domNode.offsetHeight; | ||
| const minTop = stickyScrollHeight; | ||
| const maxTop = layoutInfo.height - widgetHeight; | ||
|
|
||
| const clampedTop = Math.max(minTop, Math.min(adjustedTop, maxTop)); | ||
| const isClamped = clampedTop !== adjustedTop; | ||
| this._domNode.classList.toggle('clamped', isClamped); | ||
|
Comment on lines
+263
to
+272
|
||
|
|
||
| this._position.set({ | ||
| preference: { top: adjustedTop, left: this._anchorLeft }, | ||
| preference: { top: clampedTop, left: this._anchorLeft }, | ||
| stackOrdinal: 10000, | ||
| }, undefined); | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The observable created with
observableFromEventshould includethisas the owner parameter for better lifecycle management and debugging. While the observable will still function without it, passing the owner (similar to howobservableValueis used on line 52) ensures proper memory management and makes debugging easier. Change this to:observableFromEvent(this, stickyScrollController.onDidChangeStickyScrollHeight, () => stickyScrollController.stickyScrollWidgetHeight)