Skip to content
Open
60 changes: 59 additions & 1 deletion src/idd/ui/scrollable_area.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
from __future__ import annotations
import os
import time
from textual.widgets import RichLog
from textual import events
from textual.reactive import reactive


class TextScrollView(RichLog):
Expand All @@ -10,19 +14,73 @@ class TextScrollView(RichLog):
DEFAULT_CSS = """
TextScrollView {
height: 100%;
scrollbar-size: 1 1;
scrollbar-size: 1 1;
}
"""

# Terminal-specific selection tips
SELECTION_TIPS = {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can we detect which app we are running and display the relevant tip?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes, this is what is done currently(?)
For the "default" which is the Mac terminal, I haven't figured out the command and I don't think it's possible just with a command, that's why I put a general message for now.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, sounds good. Do we need to enumerate the shortcuts by hand -- isn't there some api that we can use? I bet there is some way to configure these on the user end...

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I unfortunately couldn't find any api... I will definitely keep it in mind for later though

"Terminal.app": "Option(⌥)+Click to select text",
"iTerm2": "Cmd(⌘)+Shift+C: Copy mode | Option(⌥)+Click: Selection",
"Warp": "Shift+Click to select text",
"default": "Use terminal's selection mechanism to copy text" #default mac terminal
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
"default": "Use terminal's selection mechanism to copy text" #default mac terminal
"default": "Use terminal's selection mechanism to copy text (Maybe Shift+Click)" #default mac terminal

Does this sound better?

}

# Track if we've shown the tip for this panel
tip_shown = reactive(False)

def __init__(self, title: str = "", component_id: str = None) -> None:
super().__init__(name=title, auto_scroll=True, markup=True)
self.border_title = title
if component_id:
self.id = component_id
self._hover_start = 0

def append(self, lines: list[str]):
self.write("\n".join(lines))

def text(self, lines: list[str]):
self.clear()
self.append(lines)

def _get_terminal_type(self):
"""Try to detect terminal type from environment variables."""
term_program = os.environ.get("TERM_PROGRAM", "")
if "iTerm" in term_program:
return "iTerm2"
elif "Apple_Terminal" in term_program:
return "Terminal.app"
elif "WarpTerminal" in term_program:
return "Warp"
return "default"

def on_mouse_down(self, event: events.MouseDown) -> None:
"""Track when the user clicks on the panel."""
self._clicked = True
self._hover_start = time.time() # Start timing from the click

def on_mouse_move(self, event: events.MouseMove) -> None:
"""Show selection tip after clicking and hovering."""
# Only show tip if we've been clicked and haven't shown a tip yet
if hasattr(self, '_clicked') and self._clicked and not self.tip_shown:
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's show the tip multiple times. I will need it, I can't remember anything 😄.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@Vipul-Cariappa I have tried this by setting tip_shown to false again in on_mouse_up function. The other issue I have is about the tip showing multiple times (depending on the pane I scroll+click). So I get multiple tips(one on top of the other) all showing at the same time. This comes from the fact that each pane initialises its own TextScrollView in DiffDebug. How would you go about this ? I have tried using global variables but it still seems to be attached to each instance of TextScrollView. I thought about maybe moving the tip_shown variable to DiffDebug ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I did not realise you were modifying TextScrollView. I believe you will have to move all these changes to DiffDebug. It belongs there.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok thanks!

current_time = time.time()
hover_duration = current_time - self._hover_start

# Show tip after 0.1 seconds of hovering after a click
if hover_duration > 0.1:
terminal = self._get_terminal_type()
tip = self.SELECTION_TIPS.get(terminal, self.SELECTION_TIPS["default"])
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be part of __init__. Why recompute this again?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Makes sense, thanks!

self.notify(tip, severity="information", timeout=5)
self.tip_shown = True

def on_mouse_up(self, event: events.MouseUp) -> None:
"""Reset click state when mouse button is released."""
if hasattr(self, '_clicked'):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Initialize self._clicked in __init__. And remove the hasattr.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks!

self._clicked = False

def on_leave(self, event: events.Leave) -> None:
"""Reset hover timer and click state when mouse leaves the widget."""
self._hover_start = 0
if hasattr(self, '_clicked'):
self._clicked = False
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Same as above.