Merge branch 'master' into configurable-hotkeys
This commit is contained in:
commit
974ca4ecc6
24 changed files with 1490 additions and 787 deletions
17
src/main.rs
17
src/main.rs
|
|
@ -2934,6 +2934,15 @@ impl Application for App {
|
|||
self.core.window.show_context = !self.core.window.show_context;
|
||||
self.pane_model.update_terminal_focus();
|
||||
|
||||
#[cfg(feature = "password_manager")]
|
||||
if ContextPage::PasswordManager == context_page {
|
||||
if self.core.window.show_context {
|
||||
self.password_mgr.pane = Some(self.pane_model.focused());
|
||||
return self.password_mgr.refresh_password_list();
|
||||
} else {
|
||||
self.password_mgr.clear();
|
||||
}
|
||||
}
|
||||
return self.update_focus();
|
||||
} else {
|
||||
self.context_page = context_page;
|
||||
|
|
@ -2968,12 +2977,8 @@ impl Application for App {
|
|||
|
||||
#[cfg(feature = "password_manager")]
|
||||
if ContextPage::PasswordManager == context_page {
|
||||
if self.core.window.show_context {
|
||||
self.password_mgr.pane = Some(self.pane_model.focused());
|
||||
return self.password_mgr.refresh_password_list();
|
||||
} else {
|
||||
self.password_mgr.clear();
|
||||
}
|
||||
self.password_mgr.pane = Some(self.pane_model.focused());
|
||||
return self.password_mgr.refresh_password_list();
|
||||
}
|
||||
}
|
||||
Message::UpdateDefaultProfile((default, profile_id)) => {
|
||||
|
|
|
|||
|
|
@ -204,7 +204,7 @@ pub fn menu_bar<'a>(
|
|||
|
||||
responsive_menu_bar()
|
||||
.item_height(ItemHeight::Dynamic(40))
|
||||
.item_width(ItemWidth::Uniform(240))
|
||||
.item_width(ItemWidth::Uniform(320))
|
||||
.spacing(4.0)
|
||||
.into_element(
|
||||
core,
|
||||
|
|
|
|||
|
|
@ -246,6 +246,7 @@ pub struct Terminal {
|
|||
pub url_regex_search: RegexSearch,
|
||||
pub regex_matches: Vec<alacritty_terminal::term::search::Match>,
|
||||
pub active_regex_match: Option<alacritty_terminal::term::search::Match>,
|
||||
pub active_hyperlink_id: Option<String>,
|
||||
bold_font_weight: Weight,
|
||||
buffer: Arc<Buffer>,
|
||||
is_focused: bool,
|
||||
|
|
@ -335,6 +336,7 @@ impl Terminal {
|
|||
|
||||
Ok(Self {
|
||||
active_regex_match: None,
|
||||
active_hyperlink_id: None,
|
||||
url_regex_search: url_regex_search(),
|
||||
regex_matches: Vec::new(),
|
||||
bold_font_weight: Weight(bold_font_weight),
|
||||
|
|
@ -887,6 +889,28 @@ impl Terminal {
|
|||
flags |= Flags::UNDERLINE;
|
||||
}
|
||||
}
|
||||
if let Some(active_id) = &self.active_hyperlink_id {
|
||||
let mut matches_active = indexed
|
||||
.cell
|
||||
.hyperlink()
|
||||
.is_some_and(|link| link.id() == active_id);
|
||||
if !matches_active
|
||||
&& indexed.cell.flags.intersects(
|
||||
Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER,
|
||||
)
|
||||
&& indexed.point.column.0 > 0
|
||||
{
|
||||
matches_active = grid[Point::new(
|
||||
indexed.point.line,
|
||||
Column(indexed.point.column.0 - 1),
|
||||
)]
|
||||
.hyperlink()
|
||||
.is_some_and(|link| link.id() == active_id);
|
||||
}
|
||||
if matches_active {
|
||||
flags |= Flags::UNDERLINE;
|
||||
}
|
||||
}
|
||||
|
||||
let metadata = Metadata::new(bg, fg)
|
||||
.with_flags(flags)
|
||||
|
|
|
|||
|
|
@ -343,11 +343,7 @@ where
|
|||
|
||||
let location = terminal
|
||||
.viewport_to_point(TermPoint::new(row as usize, TermColumn(col as usize)));
|
||||
if terminal
|
||||
.regex_matches
|
||||
.iter()
|
||||
.any(|bounds| bounds.contains(&location))
|
||||
{
|
||||
if get_hyperlink(&terminal, location).is_some() {
|
||||
return mouse::Interaction::Pointer;
|
||||
}
|
||||
}
|
||||
|
|
@ -731,66 +727,75 @@ where
|
|||
state.scrollbar_rect.set(Rectangle::default())
|
||||
}
|
||||
|
||||
// Draw cursor
|
||||
// Draw cursor (only when not scrolled, as cursor is at bottom of active area)
|
||||
{
|
||||
let cursor = terminal.term.lock().renderable_content().cursor;
|
||||
let col = cursor.point.column.0;
|
||||
let line = cursor.point.line.0;
|
||||
let color = terminal.term.lock().colors()[NamedColor::Cursor]
|
||||
.or(terminal.colors()[NamedColor::Cursor])
|
||||
.map(|rgb| Color::from_rgb8(rgb.r, rgb.g, rgb.b))
|
||||
.unwrap_or(Color::WHITE); // TODO default color from theme?
|
||||
let width = terminal.size().cell_width;
|
||||
let height = terminal.size().cell_height;
|
||||
let top_left = view_position
|
||||
+ Vector::new((col as f32 * width).floor(), (line as f32 * height).floor());
|
||||
match cursor.shape {
|
||||
CursorShape::Beam => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(1.0, height)),
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, color);
|
||||
}
|
||||
CursorShape::Underline => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(
|
||||
view_position
|
||||
+ Vector::new(
|
||||
(col as f32 * width).floor(),
|
||||
((line + 1) as f32 * height).floor(),
|
||||
),
|
||||
Size::new(width, 1.0),
|
||||
),
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, color);
|
||||
}
|
||||
CursorShape::Block if !state.is_focused => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(width, height)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color,
|
||||
let term = terminal.term.lock();
|
||||
let display_offset = term.grid().display_offset();
|
||||
let cursor = term.renderable_content().cursor;
|
||||
drop(term);
|
||||
|
||||
// Skip drawing cursor when scrolled - the cursor is below the visible viewport
|
||||
if display_offset > 0 {
|
||||
// Cursor is off-screen when scrolled up
|
||||
} else {
|
||||
let col = cursor.point.column.0;
|
||||
let line = cursor.point.line.0;
|
||||
let color = terminal.term.lock().colors()[NamedColor::Cursor]
|
||||
.or(terminal.colors()[NamedColor::Cursor])
|
||||
.map(|rgb| Color::from_rgb8(rgb.r, rgb.g, rgb.b))
|
||||
.unwrap_or(Color::WHITE); // TODO default color from theme?
|
||||
let width = terminal.size().cell_width;
|
||||
let height = terminal.size().cell_height;
|
||||
let top_left = view_position
|
||||
+ Vector::new((col as f32 * width).floor(), (line as f32 * height).floor());
|
||||
match cursor.shape {
|
||||
CursorShape::Beam => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(1.0, height)),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, Color::TRANSPARENT);
|
||||
}
|
||||
CursorShape::HollowBlock => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(width, height)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color,
|
||||
};
|
||||
renderer.fill_quad(quad, color);
|
||||
}
|
||||
CursorShape::Underline => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(
|
||||
view_position
|
||||
+ Vector::new(
|
||||
(col as f32 * width).floor(),
|
||||
((line + 1) as f32 * height).floor(),
|
||||
),
|
||||
Size::new(width, 1.0),
|
||||
),
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, Color::TRANSPARENT);
|
||||
};
|
||||
renderer.fill_quad(quad, color);
|
||||
}
|
||||
CursorShape::Block if !state.is_focused => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(width, height)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, Color::TRANSPARENT);
|
||||
}
|
||||
CursorShape::HollowBlock => {
|
||||
let quad = Quad {
|
||||
bounds: Rectangle::new(top_left, Size::new(width, height)),
|
||||
border: Border {
|
||||
width: 1.0,
|
||||
color,
|
||||
..Default::default()
|
||||
},
|
||||
..Default::default()
|
||||
};
|
||||
renderer.fill_quad(quad, Color::TRANSPARENT);
|
||||
}
|
||||
CursorShape::Block | CursorShape::Hidden => {} // Block is handled seperately
|
||||
}
|
||||
CursorShape::Block | CursorShape::Hidden => {} // Block is handled seperately
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -1009,12 +1014,15 @@ where
|
|||
Event::Keyboard(KeyEvent::ModifiersChanged(modifiers)) => {
|
||||
state.modifiers = modifiers;
|
||||
|
||||
if modifiers.contains(Modifiers::CTRL) || terminal.active_regex_match.is_some() {
|
||||
if modifiers.contains(Modifiers::CTRL)
|
||||
|| terminal.active_regex_match.is_some()
|
||||
|| terminal.active_hyperlink_id.is_some()
|
||||
{
|
||||
//Might need to update the url regex highlight,
|
||||
//so we need to calculate the mouse position
|
||||
let location = if let Some(p) = cursor_position.position() {
|
||||
let x = (p.x - layout.bounds().x) - self.padding.left;
|
||||
let y = (p.y - layout.bounds().y) - self.padding.top;
|
||||
let location = if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||
let x = p.x - self.padding.left;
|
||||
let y = p.y - self.padding.top;
|
||||
//TODO: better calculation of position
|
||||
let col = x / terminal.size().cell_width;
|
||||
let row = y / terminal.size().cell_height;
|
||||
|
|
@ -1152,18 +1160,30 @@ where
|
|||
} else {
|
||||
TermSide::Right
|
||||
};
|
||||
let selection = match click_kind {
|
||||
ClickKind::Single => {
|
||||
Selection::new(SelectionType::Simple, location, side)
|
||||
// Check if shift is pressed and there's an existing selection to extend
|
||||
if state.modifiers.shift() {
|
||||
let mut term = terminal.term.lock();
|
||||
if let Some(ref mut selection) = term.selection {
|
||||
selection.update(location, side);
|
||||
} else {
|
||||
term.selection = Some(Selection::new(
|
||||
SelectionType::Simple,
|
||||
location,
|
||||
side,
|
||||
));
|
||||
}
|
||||
ClickKind::Double => {
|
||||
Selection::new(SelectionType::Semantic, location, side)
|
||||
}
|
||||
ClickKind::Triple => {
|
||||
Selection::new(SelectionType::Lines, location, side)
|
||||
}
|
||||
};
|
||||
{
|
||||
} else {
|
||||
let selection = match click_kind {
|
||||
ClickKind::Single => {
|
||||
Selection::new(SelectionType::Simple, location, side)
|
||||
}
|
||||
ClickKind::Double => {
|
||||
Selection::new(SelectionType::Semantic, location, side)
|
||||
}
|
||||
ClickKind::Triple => {
|
||||
Selection::new(SelectionType::Lines, location, side)
|
||||
}
|
||||
};
|
||||
let mut term = terminal.term.lock();
|
||||
term.selection = Some(selection);
|
||||
}
|
||||
|
|
@ -1311,29 +1331,38 @@ where
|
|||
self.mouse_inside_boundary = Some(mouse_is_inside);
|
||||
}
|
||||
}
|
||||
if let Some(p) = cursor_position.position() {
|
||||
if let Some(p_global) = cursor_position.position() {
|
||||
let bounds = layout.bounds();
|
||||
let x = (p.x - bounds.x) - self.padding.left;
|
||||
let y = (p.y - bounds.y) - self.padding.top;
|
||||
//TODO: better calculation of position
|
||||
let col = x / terminal.size().cell_width;
|
||||
let row = y / terminal.size().cell_height;
|
||||
let location = terminal
|
||||
.viewport_to_point(TermPoint::new(row as usize, TermColumn(col as usize)));
|
||||
update_active_regex_match(
|
||||
&mut terminal,
|
||||
Some(location),
|
||||
Some(&state.modifiers),
|
||||
);
|
||||
let col_row_opt = if let Some(p) = cursor_position.position_in(bounds) {
|
||||
let x = p.x - self.padding.left;
|
||||
let y = p.y - self.padding.top;
|
||||
//TODO: better calculation of position
|
||||
let col = x / terminal.size().cell_width;
|
||||
let row = y / terminal.size().cell_height;
|
||||
let location = terminal.viewport_to_point(TermPoint::new(
|
||||
row as usize,
|
||||
TermColumn(col as usize),
|
||||
));
|
||||
update_active_regex_match(
|
||||
&mut terminal,
|
||||
Some(location),
|
||||
Some(&state.modifiers),
|
||||
);
|
||||
Some((col, row))
|
||||
} else {
|
||||
None
|
||||
};
|
||||
|
||||
if is_mouse_mode {
|
||||
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||
if let Some((col, row)) = col_row_opt {
|
||||
terminal.report_mouse(event, &state.modifiers, col as u32, row as u32);
|
||||
}
|
||||
} else {
|
||||
let handled_buffer_drag = update_buffer_drag(
|
||||
state,
|
||||
&mut terminal,
|
||||
buffer_size,
|
||||
p,
|
||||
p_global,
|
||||
bounds,
|
||||
self.padding,
|
||||
0.0,
|
||||
|
|
@ -1345,6 +1374,7 @@ where
|
|||
start_scroll,
|
||||
}) = state.dragging.as_mut()
|
||||
{
|
||||
let y = p_global.y - bounds.y - self.padding.top;
|
||||
let start_y = *start_y;
|
||||
let start_scroll = *start_scroll;
|
||||
let scroll_offset = terminal.with_buffer(|buffer| {
|
||||
|
|
@ -1359,9 +1389,9 @@ where
|
|||
state.autoscroll.stop();
|
||||
} else {
|
||||
if state.autoscroll.is_active() {
|
||||
state.autoscroll.update_pointer(p);
|
||||
state.autoscroll.update_pointer(p_global);
|
||||
} else {
|
||||
state.autoscroll.start(p);
|
||||
state.autoscroll.start(p_global);
|
||||
}
|
||||
shell.request_redraw(RedrawRequest::NextFrame);
|
||||
}
|
||||
|
|
@ -1374,8 +1404,8 @@ where
|
|||
Event::Mouse(MouseEvent::WheelScrolled { delta }) => {
|
||||
if let Some(p) = cursor_position.position_in(layout.bounds()) {
|
||||
if is_mouse_mode {
|
||||
let x = (p.x - layout.bounds().x) - self.padding.left;
|
||||
let y = (p.y - layout.bounds().y) - self.padding.top;
|
||||
let x = p.x - self.padding.left;
|
||||
let y = p.y - self.padding.top;
|
||||
//TODO: better calculation of position
|
||||
let col = x / terminal.size().cell_width;
|
||||
let row = y / terminal.size().cell_height;
|
||||
|
|
@ -1449,6 +1479,9 @@ fn get_hyperlink(
|
|||
terminal: &std::sync::MutexGuard<'_, Terminal>,
|
||||
location: TermPoint,
|
||||
) -> Option<String> {
|
||||
if let Some(link) = osc8_hyperlink_at(terminal, location) {
|
||||
return Some(link.uri().to_string());
|
||||
}
|
||||
if let Some(match_) = terminal
|
||||
.regex_matches
|
||||
.iter()
|
||||
|
|
@ -1461,6 +1494,37 @@ fn get_hyperlink(
|
|||
}
|
||||
}
|
||||
|
||||
fn get_hyperlink_id(
|
||||
terminal: &std::sync::MutexGuard<'_, Terminal>,
|
||||
location: TermPoint,
|
||||
) -> Option<String> {
|
||||
osc8_hyperlink_at(terminal, location).map(|link| link.id().to_string())
|
||||
}
|
||||
|
||||
fn osc8_hyperlink_at(
|
||||
terminal: &std::sync::MutexGuard<'_, Terminal>,
|
||||
location: TermPoint,
|
||||
) -> Option<alacritty_terminal::term::cell::Hyperlink> {
|
||||
let term = terminal.term.lock();
|
||||
if location.line >= term.screen_lines() || location.column.0 >= term.columns() {
|
||||
return None;
|
||||
}
|
||||
let grid = term.grid();
|
||||
let cell = &grid[location];
|
||||
if let Some(link) = cell.hyperlink() {
|
||||
return Some(link);
|
||||
}
|
||||
if cell
|
||||
.flags
|
||||
.intersects(Flags::WIDE_CHAR_SPACER | Flags::LEADING_WIDE_CHAR_SPACER)
|
||||
&& location.column.0 > 0
|
||||
{
|
||||
let left = TermPoint::new(location.line, TermColumn(location.column.0 - 1));
|
||||
return grid[left].hyperlink();
|
||||
}
|
||||
None
|
||||
}
|
||||
|
||||
fn update_active_regex_match(
|
||||
terminal: &mut std::sync::MutexGuard<'_, Terminal>,
|
||||
location: Option<TermPoint>,
|
||||
|
|
@ -1473,6 +1537,9 @@ fn update_active_regex_match(
|
|||
return;
|
||||
}
|
||||
|
||||
let allow_hyperlink = modifiers
|
||||
.map(|mods| mods.contains(Modifiers::CTRL))
|
||||
.unwrap_or(false);
|
||||
//Require CTRL for keyboard and mouse interaction
|
||||
if let Some(modifiers) = modifiers {
|
||||
if !modifiers.contains(Modifiers::CTRL) {
|
||||
|
|
@ -1480,16 +1547,37 @@ fn update_active_regex_match(
|
|||
terminal.active_regex_match = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
if terminal.active_hyperlink_id.is_some() {
|
||||
terminal.active_hyperlink_id = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
return;
|
||||
}
|
||||
} else if terminal.active_hyperlink_id.is_some() {
|
||||
terminal.active_hyperlink_id = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
let Some(location) = location else {
|
||||
if terminal.active_regex_match.is_some() {
|
||||
terminal.active_regex_match = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
if terminal.active_hyperlink_id.is_some() {
|
||||
terminal.active_hyperlink_id = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
return;
|
||||
};
|
||||
if allow_hyperlink {
|
||||
let next_hyperlink_id = get_hyperlink_id(terminal, location);
|
||||
if terminal.active_hyperlink_id != next_hyperlink_id {
|
||||
terminal.active_hyperlink_id = next_hyperlink_id;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
} else if terminal.active_hyperlink_id.is_some() {
|
||||
terminal.active_hyperlink_id = None;
|
||||
terminal.needs_update = true;
|
||||
}
|
||||
if let Some(match_) = terminal
|
||||
.regex_matches
|
||||
.iter()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue