Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
52 changes: 51 additions & 1 deletion frontend/app/view/term/term.scss
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,16 @@
min-height: 0;
overflow: hidden;
line-height: 1;
margin: 5px;
margin-top: 5px;
margin-bottom: 5px;
margin-left: 4px;
margin-right: 0; // Remove margin-right, use padding-right instead
position: relative; // Ensure positioning context for absolute children
// Use padding-right instead of margin-right to ensure fixed spacing
// Padding is part of the element's box model and won't be compressed by flexbox
// This ensures the scrollbar maintains a consistent right spacing regardless of window width
padding-right: 5px; // Fixed padding to maintain scrollbar right spacing
box-sizing: border-box; // Ensure padding is included in width calculation
}

.term-htmlelem {
Expand Down Expand Up @@ -147,18 +155,25 @@
}

.terminal {
// Ensure terminal container respects parent margin
width: 100%;
height: 100%;

.xterm-viewport {
&::-webkit-scrollbar {
width: 6px;
height: 6px;
}

&::-webkit-scrollbar-track {
// Make track slightly transparent so search match indicators are visible
background-color: var(--scrollbar-background-color);
opacity: 0.4; // Allow search match indicators to show through
}

&::-webkit-scrollbar-thumb {
display: none;
// Use original CSS variables for default and hover states
background-color: var(--scrollbar-thumb-color);
border-radius: 4px;
margin: 0 1px 0 1px;
Expand All @@ -171,6 +186,19 @@
background-color: var(--scrollbar-thumb-active-color);
}
}

// Increase opacity when scrolling
&.scrolling::-webkit-scrollbar-thumb {
background-color: rgba(255, 255, 255, 0.5); // More opaque when scrolling

&:hover {
background-color: rgba(255, 255, 255, 0.6);
}

&:active {
background-color: rgba(255, 255, 255, 0.7);
}
}
}

&:hover {
Expand All @@ -179,4 +207,26 @@
}
}
}

// Overview ruler styles for xterm search decorations
// Ensure overview ruler is visible and positioned correctly
// The overview ruler should be positioned to the left of the scrollbar
:global(.xterm-decoration-overview-ruler) {
width: 15px !important;
min-width: 15px;
height: 100% !important;
background-color: rgba(0, 0, 0, 0.3) !important;
border-left: 1px solid rgba(255, 255, 255, 0.3) !important;
z-index: 7 !important; // Below scrollbar overlay but above decorations
right: 6px !important; // Position to the left of scrollbar (scrollbar is 6px wide)
display: block !important;
visibility: visible !important;
}

// Style for individual overview ruler markers
:global(.xterm-decoration-overview-ruler .xterm-decoration) {
width: 100% !important;
height: 2px !important;
min-height: 2px !important;
}
}
45 changes: 43 additions & 2 deletions frontend/app/view/term/term.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -186,10 +186,13 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
const searchVal = jotai.useAtomValue<string>(searchProps.searchValue);
const searchDecorations = React.useMemo(
() => ({
matchOverviewRuler: "#000000",
activeMatchColorOverviewRuler: "#000000",
matchOverviewRuler: "#FFFF00", // Yellow for all matches
activeMatchColorOverviewRuler: "#FF9632", // Orange for active match
activeMatchBorder: "#FF9632",
matchBorder: "#FFFF00",
// Optional: add background colors for better visibility
matchBackground: undefined, // Let xterm use default
activeMatchBackground: undefined, // Let xterm use default
}),
[]
);
Expand Down Expand Up @@ -276,6 +279,7 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
allowTransparency: true,
scrollback: termScrollback,
allowProposedApi: true, // Required by @xterm/addon-search to enable search functionality and decorations
overviewRulerWidth: 15, // Enable overview ruler for search match indicators
ignoreBracketedPasteMode: !termAllowBPM,
macOptionIsMeta: termMacOptionIsMeta,
},
Expand Down Expand Up @@ -328,17 +332,54 @@ const TerminalView = ({ blockId, model }: ViewComponentProps<TermViewModel>) =>
}, [isMI, isBasicTerm, isFocused]);

const scrollbarHideObserverRef = React.useRef<HTMLDivElement>(null);
const scrollTimeoutRef = React.useRef<NodeJS.Timeout | null>(null);

const onScrollbarShowObserver = React.useCallback(() => {
const termViewport = viewRef.current.getElementsByClassName("xterm-viewport")[0] as HTMLDivElement;
termViewport.style.zIndex = "var(--zindex-xterm-viewport-overlay)";
scrollbarHideObserverRef.current.style.display = "block";
}, []);

const onScrollbarHideObserver = React.useCallback(() => {
const termViewport = viewRef.current.getElementsByClassName("xterm-viewport")[0] as HTMLDivElement;
termViewport.style.zIndex = "auto";
scrollbarHideObserverRef.current.style.display = "none";
}, []);

// Handle scrollbar opacity during scrolling
React.useEffect(() => {
if (!viewRef.current) return;

const viewport = viewRef.current.getElementsByClassName("xterm-viewport")[0] as HTMLElement;
if (!viewport) return;

const handleScroll = () => {
// Add scrolling class when user scrolls
viewport.classList.add("scrolling");

// Clear existing timeout
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}

// Remove scrolling class after scroll stops (300ms delay)
scrollTimeoutRef.current = setTimeout(() => {
viewport.classList.remove("scrolling");
}, 300);
};

viewport.addEventListener("scroll", handleScroll, { passive: true });
viewport.addEventListener("wheel", handleScroll, { passive: true });

return () => {
viewport.removeEventListener("scroll", handleScroll);
viewport.removeEventListener("wheel", handleScroll);
if (scrollTimeoutRef.current) {
clearTimeout(scrollTimeoutRef.current);
}
};
}, [viewRef]);

const stickerConfig = {
charWidth: 8,
charHeight: 16,
Expand Down