Change:
Testing Tiniest of nits, and perhaps this is inherited from all hovers, but I felt like the hover disappeared too quickly. If I was reading particularly slowly or looked away and then back at the message, I might've missed it.
felt the same, I think using the workbench hover infra would help.
Also, it disappears when you still hover on it: !
I think given the new complexity of this hover and the actions we should do a tactical fix here. I suggest to remove the timer that removes the message automatically after 3 seconds: There are still other listeners in place to hide the message, so I think we are good.
makes sense.
trying to verify: "readonly hover" means "hover in readonly files" ?
best to verify would be in repo open a file from folder and try to edit in such a file. You should see a hover with a link.

Discussion:
} .monaco-editor .monaco-editor-overlaymessage .message { <del> padding: 1px 4px; color: var(--vscode-inputValidation-infoForeground); background-color: var(--vscode-inputValidation-infoBackground); </del> <ins> padding: 2px 4px; color: var(--vscode-editorHoverWidget-foreground); background-color: var(--vscode-editorHoverWidget-background); </ins> border: 1px solid var(--vscode-inputValidation-infoBorder); <ins> border-radius: 3px; </ins> } .monaco-editor .monaco-editor-overlaymessage .message p {

} .monaco-editor .monaco-editor-overlaymessage .message a { <del> font-weight: bold; color: var(--vscode-inputValidation-infoForeground); </del> <ins> color: var(--vscode-textLink-foreground); } .monaco-editor .monaco-editor-overlaymessage .message a:hover { color: var(--vscode-textLink-activeForeground); </ins> } .monaco-editor.hc-black .monaco-editor-overlaymessage .message,

z-index: 1000; border-width: 8px; position: absolute; <ins> left: 2px; </ins> } .monaco-editor .monaco-editor-overlaymessage .anchor.top {

import { renderMarkdown } from 'vs/base/browser/markdownRenderer'; import { alert } from 'vs/base/browser/ui/aria/aria'; <del> import { TimeoutTimer } from 'vs/base/common/async'; </del> <ins> import { Event } from 'vs/base/common/event'; </ins> import { IMarkdownString, isMarkdownString } from 'vs/base/common/htmlContent'; import { KeyCode } from 'vs/base/common/keyCodes'; import { DisposableStore, IDisposable, MutableDisposable } from 'vs/base/common/lifecycle';

private readonly _messageWidget = new MutableDisposable<MessageWidget>(); private readonly _messageListeners = new DisposableStore(); private _message: { element: HTMLElement; dispose: () => void } | undefined; <del> private _focus: boolean = false; </del> <ins> private _mouseOverMessage: boolean = false; </ins> constructor( editor: ICodeEditor,

}) : undefined; this._messageWidget.value = new MessageWidget(this._editor, position, typeof message === 'string' ? message : this._message!.element); <del> // close on blur, cursor, model change, dispose this._messageListeners.add(this._editor.onDidBlurEditorText(() => { if (!this._focus) { this.closeMessage(); </del> <ins> // close on blur (debounced to allow to tab into the message), cursor, model change, dispose this._messageListeners.add(Event.debounce(this._editor.onDidBlurEditorText, (last, event) => event, 0)(() => { if (this._mouseOverMessage) { return; // override when mouse over message } if (this._messageWidget.value && dom.isAncestor(document.activeElement, this._messageWidget.value.getDomNode())) { return; // override when focus is inside the message </ins> } <ins> this.closeMessage(); </ins> } )); this._messageListeners.add(this._editor.onDidChangeCursorPosition(() => this.closeMessage())); this._messageListeners.add(this._editor.onDidDispose(() => this.closeMessage())); this._messageListeners.add(this._editor.onDidChangeModel(() => this.closeMessage())); <del> this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_ENTER, () => this._focus = true, true)); this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_LEAVE, () => this._focus = false, true)); // 3sec this._messageListeners.add(new TimeoutTimer(() => this.closeMessage(), 3000)); </del> <ins> this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_ENTER, () => this._mouseOverMessage = true, true)); this._messageListeners.add(dom.addDisposableListener(this._messageWidget.value.getDomNode(), dom.EventType.MOUSE_LEAVE, () => this._mouseOverMessage = false, true)); </ins> // close on mouse move let bounds: Range;