353 lines
14 KiB
Rust
353 lines
14 KiB
Rust
//! Grid tooltips for the terminal view
|
|
|
|
use galaxyui::elements::{
|
|
ChildAnchor, Dismiss, MouseStateHandle, OffsetPositioning, PositionedElementAnchor,
|
|
PositionedElementOffsetBounds, Stack,
|
|
};
|
|
use galaxyui::SingletonEntity;
|
|
use pathfinder_geometry::vector::vec2f;
|
|
use warpui::{AppContext, Element, EventContext};
|
|
|
|
use super::{TerminalAction, TerminalView};
|
|
use crate::appearance::Appearance;
|
|
use crate::terminal::links::directly_open_link_keybinding_string;
|
|
use crate::terminal::model::{ObfuscateSecrets, Secret};
|
|
use crate::terminal::safe_mode_settings::get_secret_obfuscation_mode;
|
|
use crate::terminal::view::SecretTooltip;
|
|
use crate::terminal::TerminalModel;
|
|
use crate::util::tooltips::{TooltipLink, TooltipRedaction};
|
|
|
|
cfg_if::cfg_if! {
|
|
if #[cfg(feature = "local_fs")] {
|
|
use crate::terminal::view::RichContentLink;
|
|
use super::GridHighlightedLink;
|
|
}
|
|
}
|
|
|
|
struct GridTooltipLink {
|
|
text: String,
|
|
action: TerminalAction,
|
|
/// Optional detail text to show after the link.
|
|
detail: Option<String>,
|
|
mouse_state: MouseStateHandle,
|
|
}
|
|
|
|
/// If appropriate, returns a GridTooltipLink for opening the file in warp.
|
|
/// Mutates `detail_for_default` leaving None in place if the GridTooltipLink returned is the default
|
|
/// action on "Cmd+Click" and thus should use the detail_for_default.
|
|
#[cfg(feature = "local_fs")]
|
|
fn open_in_warp_tooltip(
|
|
path: std::path::PathBuf,
|
|
line_and_column_num: Option<galaxy_util::path::LineAndColumnArg>,
|
|
detail_for_default: &mut Option<String>,
|
|
mouse_state: MouseStateHandle,
|
|
app: &AppContext,
|
|
) -> Option<GridTooltipLink> {
|
|
use settings::Setting as _;
|
|
|
|
use crate::settings::CodeSettings;
|
|
use crate::util::file::external_editor::EditorSettings;
|
|
use crate::util::tooltips::should_show_open_in_warp_link;
|
|
|
|
if !should_show_open_in_warp_link(&path, app) {
|
|
return None;
|
|
}
|
|
|
|
let detail = if *CodeSettings::as_ref(app).code_as_default_editor.value() {
|
|
detail_for_default.take()
|
|
} else {
|
|
None
|
|
};
|
|
Some(GridTooltipLink {
|
|
text: "Open in Galaxy".to_string(),
|
|
action: TerminalAction::OpenCodeInWarp {
|
|
path,
|
|
layout: *EditorSettings::as_ref(app).open_file_layout.value(),
|
|
line_col: line_and_column_num,
|
|
},
|
|
mouse_state,
|
|
detail,
|
|
})
|
|
}
|
|
|
|
/// Returns a GridTooltipLink for revealing the file in the platform's file explorer
|
|
/// (Finder on macOS, file manager on Linux/Windows).
|
|
#[cfg(feature = "local_fs")]
|
|
fn show_in_file_explorer_tooltip(
|
|
path: std::path::PathBuf,
|
|
mouse_state: MouseStateHandle,
|
|
) -> GridTooltipLink {
|
|
let text = if cfg!(target_os = "macos") {
|
|
"Show in Finder"
|
|
} else {
|
|
"Show containing folder"
|
|
}
|
|
.to_string();
|
|
GridTooltipLink {
|
|
text,
|
|
action: TerminalAction::ShowInFileExplorer(path),
|
|
mouse_state,
|
|
detail: None,
|
|
}
|
|
}
|
|
|
|
impl TerminalView {
|
|
/// Renders the link and/or secrets tooltips on top of the grid
|
|
/// Expects at least one of the two tooltips to be visible.
|
|
// Unused variables allowed when no local filesystem as the `app` argument
|
|
// is unused.
|
|
#[cfg_attr(not(feature = "local_fs"), allow(unused_variables))]
|
|
pub(super) fn render_grid_tooltip(
|
|
&self,
|
|
stack: &mut Stack,
|
|
model: &TerminalModel,
|
|
appearance: &Appearance,
|
|
app: &AppContext,
|
|
) {
|
|
let mut element_id = "terminal_view:first_cell_in_link".to_string();
|
|
let mut links = vec![];
|
|
let mut is_agent_conversation = false;
|
|
|
|
if let Some(open_secret_tooltip) = &self.open_secret_tool_tip {
|
|
match open_secret_tooltip {
|
|
SecretTooltip::Grid {
|
|
tooltip,
|
|
is_agent_mode,
|
|
} => {
|
|
let handle = *tooltip;
|
|
is_agent_conversation = *is_agent_mode;
|
|
|
|
// Position the tooltip above the first cell in the secret.
|
|
element_id = format!(
|
|
"terminal_view:first_cell_in_secret_{}",
|
|
handle.get_inner().id()
|
|
);
|
|
|
|
if matches!(get_secret_obfuscation_mode(app), ObfuscateSecrets::Yes) {
|
|
let is_redacted = model
|
|
.secret_from_handle(tooltip)
|
|
.is_some_and(Secret::is_obfuscated);
|
|
|
|
if is_redacted {
|
|
links.push(GridTooltipLink {
|
|
text: "Reveal secret".to_string(),
|
|
action: TerminalAction::ToggleGridSecret {
|
|
handle,
|
|
show_secret: true,
|
|
},
|
|
mouse_state: self.mouse_states.toggle_secrets_tooltip.clone(),
|
|
detail: None,
|
|
});
|
|
} else {
|
|
links.push(GridTooltipLink {
|
|
text: "Hide secret".to_string(),
|
|
action: TerminalAction::ToggleGridSecret {
|
|
handle,
|
|
show_secret: false,
|
|
},
|
|
mouse_state: self.mouse_states.toggle_secrets_tooltip.clone(),
|
|
detail: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
links.push(GridTooltipLink {
|
|
text: "Copy secret".to_string(),
|
|
action: TerminalAction::CopyGridSecret(handle),
|
|
mouse_state: self.mouse_states.copy_secrets_tooltip.clone(),
|
|
detail: None,
|
|
});
|
|
}
|
|
SecretTooltip::RichContent {
|
|
tooltip,
|
|
is_agent_mode,
|
|
} => {
|
|
let tooltip_info = tooltip;
|
|
// Position the tooltip above the first cell in the secret.
|
|
element_id = tooltip_info.position_id.to_owned();
|
|
is_agent_conversation = *is_agent_mode;
|
|
|
|
if matches!(get_secret_obfuscation_mode(app), ObfuscateSecrets::Yes) {
|
|
let is_obfuscated = tooltip_info.is_obfuscated;
|
|
|
|
if is_obfuscated {
|
|
links.push(GridTooltipLink {
|
|
text: "Reveal secret".to_string(),
|
|
action: TerminalAction::ToggleRichContentSecret {
|
|
rich_content_tooltip_info: tooltip_info.clone(),
|
|
show_secret: true,
|
|
},
|
|
mouse_state: self.mouse_states.toggle_secrets_tooltip.clone(),
|
|
detail: None,
|
|
});
|
|
} else {
|
|
links.push(GridTooltipLink {
|
|
text: "Hide secret".to_string(),
|
|
action: TerminalAction::ToggleRichContentSecret {
|
|
rich_content_tooltip_info: tooltip_info.clone(),
|
|
show_secret: false,
|
|
},
|
|
mouse_state: self.mouse_states.toggle_secrets_tooltip.clone(),
|
|
detail: None,
|
|
})
|
|
}
|
|
}
|
|
|
|
links.push(GridTooltipLink {
|
|
text: "Copy secret".to_string(),
|
|
action: TerminalAction::CopyRichContentSecret(tooltip_info.clone()),
|
|
mouse_state: self.mouse_states.copy_secrets_tooltip.clone(),
|
|
detail: None,
|
|
});
|
|
}
|
|
}
|
|
}
|
|
|
|
#[cfg_attr(not(feature = "local_fs"), allow(unused_mut))]
|
|
if let Some(link) = &self.open_grid_link_tool_tip {
|
|
let mut open_in_warp = None;
|
|
let mut show_in_file_explorer = None;
|
|
let modifier = directly_open_link_keybinding_string();
|
|
let mut detail = Some(format!("[{modifier} Click]"));
|
|
#[cfg(feature = "local_fs")]
|
|
{
|
|
if let GridHighlightedLink::File(file_link) = link {
|
|
if let Some(path) = file_link.get_inner().absolute_path() {
|
|
open_in_warp = open_in_warp_tooltip(
|
|
path.clone(),
|
|
file_link.get_inner().line_and_column_num,
|
|
&mut detail,
|
|
self.mouse_states.open_in_warp_tooltip.clone(),
|
|
app,
|
|
);
|
|
show_in_file_explorer = Some(show_in_file_explorer_tooltip(
|
|
path,
|
|
self.mouse_states.show_in_file_explorer_tooltip.clone(),
|
|
));
|
|
}
|
|
}
|
|
}
|
|
|
|
links.push(GridTooltipLink {
|
|
text: link.tooltip_text().to_owned(),
|
|
action: TerminalAction::OpenGridLink(link.clone()),
|
|
mouse_state: self.mouse_states.grid_link_tooltip.clone(),
|
|
detail,
|
|
});
|
|
|
|
links.extend(open_in_warp);
|
|
links.extend(show_in_file_explorer);
|
|
}
|
|
|
|
#[cfg_attr(not(feature = "local_fs"), allow(unused_mut))]
|
|
if let Some(tooltip_info) = &self.open_rich_content_link_tool_tip {
|
|
element_id = tooltip_info.position_id.to_owned();
|
|
let mut open_in_warp = None;
|
|
let mut show_in_file_explorer = None;
|
|
let modifier_string = directly_open_link_keybinding_string();
|
|
let mut detail = Some(format!("[{modifier_string} Click]"));
|
|
|
|
#[cfg(feature = "local_fs")]
|
|
{
|
|
if let RichContentLink::FilePath {
|
|
absolute_path,
|
|
line_and_column_num,
|
|
..
|
|
} = &tooltip_info.link
|
|
{
|
|
open_in_warp = open_in_warp_tooltip(
|
|
absolute_path.clone(),
|
|
*line_and_column_num,
|
|
&mut detail,
|
|
self.mouse_states.open_in_warp_tooltip.clone(),
|
|
app,
|
|
);
|
|
show_in_file_explorer = Some(show_in_file_explorer_tooltip(
|
|
absolute_path.clone(),
|
|
self.mouse_states.show_in_file_explorer_tooltip.clone(),
|
|
));
|
|
}
|
|
}
|
|
|
|
links.push(GridTooltipLink {
|
|
text: tooltip_info.link.tooltip_text().to_owned(),
|
|
action: TerminalAction::OpenRichContentLink(tooltip_info.link.clone()),
|
|
mouse_state: self.mouse_states.rich_content_link_tooltip.clone(),
|
|
detail,
|
|
});
|
|
|
|
links.extend(open_in_warp);
|
|
links.extend(show_in_file_explorer);
|
|
}
|
|
|
|
let secret_redaction = get_secret_obfuscation_mode(app);
|
|
|
|
// Get the secret level from the current tooltip
|
|
let secret_level = self.open_secret_tool_tip.as_ref().and_then(|tooltip| {
|
|
match tooltip {
|
|
SecretTooltip::Grid { tooltip, .. } => {
|
|
// For grid secrets, get the secret level from the secret itself
|
|
model
|
|
.secret_from_handle(tooltip)
|
|
.map(|secret| secret.secret_level())
|
|
}
|
|
SecretTooltip::RichContent { tooltip, .. } => Some(tooltip.secret_level),
|
|
}
|
|
});
|
|
|
|
let redaction = match (
|
|
self.open_secret_tool_tip.is_some(),
|
|
secret_redaction.should_redact_secret(),
|
|
is_agent_conversation,
|
|
) {
|
|
(true, true, true) => TooltipRedaction::SecretNotSentToLLMMessaging { secret_level },
|
|
(true, true, false) => {
|
|
TooltipRedaction::SecretWillNotBeSentToLLMMessaging { secret_level }
|
|
}
|
|
(_, _, _) => TooltipRedaction::NoRedaction,
|
|
};
|
|
stack.add_positioned_overlay_child(
|
|
render_tooltip(links, redaction, appearance, app),
|
|
OffsetPositioning::offset_from_save_position_element(
|
|
element_id,
|
|
// Add a small buffer between the tooltip and the top of the cell.
|
|
vec2f(0., -2.),
|
|
PositionedElementOffsetBounds::ParentByPosition,
|
|
PositionedElementAnchor::TopLeft,
|
|
ChildAnchor::BottomLeft,
|
|
),
|
|
);
|
|
}
|
|
}
|
|
|
|
fn render_tooltip(
|
|
tooltip_links: impl IntoIterator<Item = GridTooltipLink>,
|
|
redaction: TooltipRedaction,
|
|
appearance: &Appearance,
|
|
app: &AppContext,
|
|
) -> Box<dyn Element> {
|
|
// Convert GridTooltipLink to shared TooltipLink
|
|
let shared_links = tooltip_links.into_iter().map(|link| {
|
|
let action = link.action;
|
|
TooltipLink {
|
|
text: link.text,
|
|
on_click: move |ctx: &mut EventContext| {
|
|
ctx.dispatch_typed_action(action.clone());
|
|
},
|
|
detail: link.detail,
|
|
mouse_state: link.mouse_state,
|
|
}
|
|
});
|
|
|
|
let tooltip_content =
|
|
crate::util::tooltips::render_tooltip(shared_links, redaction, appearance, app);
|
|
|
|
Dismiss::new(tooltip_content)
|
|
.on_dismiss(|ctx, _app| {
|
|
ctx.dispatch_typed_action(TerminalAction::MaybeDismissToolTip {
|
|
from_keybinding: false,
|
|
})
|
|
})
|
|
.finish()
|
|
}
|