fix(editor): keep tooltips full width at the editor's right edge (#1203)

On WebKit, CodeMirror positions tooltips absolutely inside the editor, but the tooltip space still reported the whole window as the right bound. CodeMirror never shifted a tooltip near the editor's right edge to the left, the box was squeezed into the space left of that edge, and the next measurement read the squeezed width, so it could end as a one-character column. The tooltip space is now the editor's own horizontal extent (clamped to the window) and tooltips size to their content, which fixes hover, inline blame, debugger values, signature help and hints together.

Co-authored-by: ntbang0901 <[email protected]>
This commit is contained in:
ntbang0901 authored and GitHub committed 2026-11-04 23:10:51 +08:00
1 parent bdc108a61b
commit 8f77d7077c
3 files changed
+43 -8

No files matched your search

+16 -8
View File
@@ -319,15 +319,23 @@ export interface SetupOptions {
}
/**
* Tooltips (hints, docs, completion) stay below the editor's top edge: one
* above the first lines flips below them instead of hiding under the tabs.
* Tooltips (hints, docs, completion) stay inside the editor horizontally and
* below its top edge. WebKit positions them `absolute` within `.cm-editor`, so
* a window-wide right bound makes CodeMirror skip the shift left and the
* browser squeezes the box into the space left of the editor's edge.
*/
const tooltipSpace = tooltips({
tooltipSpace: (view) => {
const top = view.scrollDOM.getBoundingClientRect().top;
return { top, left: 0, right: window.innerWidth, bottom: window.innerHeight };
},
});
export function tooltipBounds(view: { scrollDOM: Element; dom: Element }) {
const { top } = view.scrollDOM.getBoundingClientRect();
const { left, right } = view.dom.getBoundingClientRect();
return {
top,
left: Math.max(0, left),
right: Math.min(window.innerWidth, right),
bottom: window.innerHeight,
};
}
const tooltipSpace = tooltips({ tooltipSpace: tooltipBounds });
export function baseExtensions(o: SetupOptions): Extension {
return [
+3
View File
@@ -248,6 +248,9 @@ export const openideaTheme = EditorView.theme({
// Overlays
".cm-tooltip": {
// Natural width, so a measurement taken while squeezed is not fed back (#1188).
width: "max-content",
maxWidth: "100%",
backgroundColor: "var(--bg-popup)",
color: "var(--fg)",
border: "1px solid var(--border-popup)",
+24
View File
@@ -0,0 +1,24 @@
import { afterEach, beforeEach, describe, expect, it } from "vitest";
import { tooltipBounds } from "./setup";
const el = (r: Partial<DOMRect>) => ({ getBoundingClientRect: () => r as DOMRect }) as Element;
const original = window.innerWidth;
beforeEach(() => {
Object.defineProperty(window, "innerWidth", { value: 1200, configurable: true });
});
afterEach(() => {
Object.defineProperty(window, "innerWidth", { value: original, configurable: true });
});
describe("tooltipBounds", () => {
it.each([
["editor flush with the right edge", { left: 300, right: 1200 }, 300, 1200],
["narrow split pane", { left: 0, right: 400 }, 0, 400],
["editor in the middle", { left: 250, right: 750 }, 250, 750],
["editor spilling outside the window", { left: -20, right: 1500 }, 0, 1200],
])("%s", (_, rect, left, right) => {
const b = tooltipBounds({ scrollDOM: el({ top: 80 }), dom: el(rect) });
expect(b).toMatchObject({ top: 80, left, right });
});
});