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
22 changes: 0 additions & 22 deletions .github/workflows/build.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -14,33 +14,11 @@ jobs:
fail-fast: false
matrix:
include:
# Linux
- os: ubuntu-latest
name: linux
arch: x86_64
target: x86_64-unknown-linux-gnu
- os: ubuntu-latest
name: linux
arch: aarch64
target: aarch64-unknown-linux-gnu
# Windows
- os: windows-latest
name: windows
arch: x86_64
target: x86_64-pc-windows-msvc
- os: windows-latest
name: windows
arch: aarch64
target: aarch64-pc-windows-msvc
# MacOS
- os: macos-latest
name: macos
arch: x86_64
target: x86_64-apple-darwin
- os: macos-latest
name: macos
arch: aarch64
target: aarch64-apple-darwin
steps:
- uses: actions/checkout@v4
- name: Install Rust
Expand Down
1 change: 1 addition & 0 deletions crates/egui-term/src/alacritty/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -323,6 +323,7 @@ impl<'a> TerminalContext<'a> {
BackendCommand::MouseReport(button, modifiers, point, pressed) => {
self.mouse_report(button, modifiers, point, pressed);
}
_ => {}
};
}

Expand Down
3 changes: 3 additions & 0 deletions crates/egui-term/src/bindings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ pub enum BindingAction {
DecreaseFontSize,
Char(char),
Esc(String),
Search,
}

#[derive(Clone, Debug, PartialEq, Eq)]
Expand Down Expand Up @@ -334,6 +335,7 @@ fn platform_keyboard_bindings() -> Vec<(Binding<InputKind>, BindingAction)> {
Equals, Modifiers::MAC_CMD; BindingAction::IncreaseFontSize;
Plus, Modifiers::MAC_CMD; BindingAction::IncreaseFontSize;
Minus, Modifiers::MAC_CMD; BindingAction::DecreaseFontSize;
F, Modifiers::MAC_CMD; BindingAction::Search;
)
}

Expand All @@ -348,6 +350,7 @@ fn platform_keyboard_bindings() -> Vec<(Binding<InputKind>, BindingAction)> {
Equals, Modifiers::CTRL; BindingAction::IncreaseFontSize;
Plus, Modifiers::CTRL; BindingAction::IncreaseFontSize;
Minus, Modifiers::CTRL; BindingAction::DecreaseFontSize;
F, Modifiers::CTRL; BindingAction::Search;
)
}

Expand Down
6 changes: 3 additions & 3 deletions crates/egui-term/src/display/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -172,7 +172,7 @@ impl TerminalView<'_> {
#[cfg(target_os = "macos")]
let copy_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::C);
let copy_shortcut = ui.ctx().format_shortcut(&copy_shortcut);
let copy_btn = context_btn("Copy", btn_width, Some(copy_shortcut));
let copy_btn: Button<'_> = context_btn("Copy", btn_width, Some(copy_shortcut));
if ui.add(copy_btn).clicked() {
let data = self.term_ctx.selection_content();
layout.ctx.copy_text(data);
Expand All @@ -182,7 +182,7 @@ impl TerminalView<'_> {

fn paste_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) {
#[cfg(not(target_os = "macos"))]
let paste_shortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::V);
let paste_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::V);
#[cfg(target_os = "macos")]
let paste_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::V);
let paste_shortcut = ui.ctx().format_shortcut(&paste_shortcut);
Expand All @@ -198,7 +198,7 @@ impl TerminalView<'_> {

fn select_all_btn(&mut self, ui: &mut egui::Ui, btn_width: f32) {
#[cfg(not(target_os = "macos"))]
let select_all_shortcut = KeyboardShortcut::new(Modifiers::CTRL, Key::A);
let select_all_shortcut = KeyboardShortcut::new(Modifiers::CTRL | Modifiers::SHIFT, Key::A);
#[cfg(target_os = "macos")]
let select_all_shortcut = KeyboardShortcut::new(Modifiers::MAC_CMD, Key::A);
let select_all_shortcut = ui.ctx().format_shortcut(&select_all_shortcut);
Expand Down
10 changes: 10 additions & 0 deletions crates/egui-term/src/input/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,11 @@ impl TerminalView<'_> {
Some(BindingAction::SelectAll) => {
Some(InputAction::BackendCall(BackendCommand::SelectAll))
}
Some(BindingAction::Search) => {
let content = self.term_ctx.selection_content();
self.set_search_regex(content);
None
}
_ => None,
}
}
Expand All @@ -82,6 +87,11 @@ impl TerminalView<'_> {
}
}

fn set_search_regex(&mut self, str_regex: String) {
*self.options.search_start = true;
*self.options.search_regex = str_regex;
}

pub fn mouse_wheel_input(
&mut self,
state: &mut TerminalViewState,
Expand Down
2 changes: 2 additions & 0 deletions crates/egui-term/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ mod display;
mod errors;
mod font;
mod input;
mod scroll_bar;
mod ssh;
mod theme;
mod types;
Expand All @@ -13,6 +14,7 @@ pub use alacritty::{PtyEvent, TermType, Terminal, TerminalContext};
pub use alacritty_terminal::term::TermMode;
pub use bindings::{Binding, BindingAction, InputKind, KeyboardBinding};
pub use font::{FontSettings, TerminalFont};
pub use scroll_bar::{InteractiveScrollbar, ScrollbarState};
pub use ssh::{Authentication, SshOptions};
pub use theme::{ColorPalette, TerminalTheme};
pub use view::{TerminalOptions, TerminalView};
104 changes: 104 additions & 0 deletions crates/egui-term/src/scroll_bar.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
use egui::{Color32, NumExt, Pos2, Rect, Sense, Ui, Vec2};

#[derive(Clone)]
pub struct ScrollbarState {
pub scroll_pixels: f32,
}

impl Default for ScrollbarState {
fn default() -> Self {
Self { scroll_pixels: 0.0 }
}
}

pub struct InteractiveScrollbar {
pub first_row_pos: f32,
pub new_first_row_pos: Option<f32>,
}

impl InteractiveScrollbar {
pub fn new() -> Self {
Self {
first_row_pos: 0.0,
new_first_row_pos: None,
}
}

pub fn set_first_row_pos(&mut self, row: f32) {
self.first_row_pos = row;
}

pub const WIDTH: f32 = 16.0;
pub const MARGIN: f32 = 0.0;
}

impl InteractiveScrollbar {
pub fn ui(&mut self, total_height: f32, ui: &mut Ui) {
let mut position: f32;
let scrollbar_width = InteractiveScrollbar::WIDTH;
let margin = InteractiveScrollbar::MARGIN;

let available_rect = ui.available_rect_before_wrap();
let height = available_rect.bottom() - available_rect.top();
let y_min = available_rect.top() + margin;
let scrollbar_rect = Rect::from_min_size(
Pos2::new(available_rect.right() - scrollbar_width - margin, y_min),
Vec2::new(scrollbar_width, height),
);

let ratio = (height / total_height).min(1.0);
let slider_height = (height * ratio).at_least(64.0);
let max_value = total_height - height;
let max_scroll_top = height - slider_height;
let scroll_pos = max_scroll_top - self.first_row_pos * max_scroll_top / max_value;
let slider_rect = Rect::from_min_size(
scrollbar_rect.min + Vec2::new(0.0, scroll_pos),
Vec2::new(scrollbar_width, slider_height),
);

ui.painter().rect_filled(
scrollbar_rect,
0.0,
Color32::BLACK, //from_gray(100)
);

ui.painter().rect_filled(
slider_rect,
0.0,
Color32::DARK_GRAY, //from_gray(200)
);

let response = ui.allocate_rect(slider_rect, Sense::click_and_drag());

let scrollbar_response = ui.allocate_rect(scrollbar_rect, Sense::click());

if response.dragged() {
if let Some(pos) = response.hover_pos() {
let new_position = pos.y - scrollbar_rect.top();
position = new_position.clamp(0.0, height);
let new_first_row_pos = max_value - position * max_value / max_scroll_top;
self.new_first_row_pos = Some(new_first_row_pos);
}
}

if scrollbar_response.clicked() {
if let Some(click_pos) = scrollbar_response.interact_pointer_pos() {
let click_y = click_pos.y - scrollbar_rect.top();
position = click_y.clamp(0.0, height);
let new_first_row_pos = max_value - position * max_value / max_scroll_top;
self.new_first_row_pos = Some(new_first_row_pos);
}
}

// mouse wheel
/*
let scroll_delta = ui.input(|i| i.smooth_scroll_delta.y);
if scroll_delta != 0.0 {
self.state.position += scroll_delta * 1.0;
self.state.position = self.state.position.clamp(0.0, height);
}
*/

ui.ctx().request_repaint();
}
}
110 changes: 80 additions & 30 deletions crates/egui-term/src/view.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,11 @@ use crate::bindings::Binding;
use crate::bindings::{BindingAction, Bindings, InputKind};
use crate::font::TerminalFont;
use crate::input::InputAction;
use crate::scroll_bar::{InteractiveScrollbar, ScrollbarState};
use crate::theme::TerminalTheme;
use crate::types::Size;
use alacritty_terminal::index::Point;
use alacritty_terminal::grid::{Dimensions, Scroll};
use alacritty_terminal::index::{Column, Line, Point};
use egui::ImeEvent;
use egui::Widget;
use egui::{Context, Event};
Expand All @@ -22,6 +24,7 @@ pub struct TerminalViewState {
pub cursor_position: Option<Pos2>,
// ime_enabled: bool,
// ime_cursor_range: CursorRange,
pub scrollbar_state: ScrollbarState,
}

impl TerminalViewState {
Expand Down Expand Up @@ -53,42 +56,86 @@ pub struct TerminalOptions<'a> {
pub multi_exec: &'a mut bool,
pub theme: &'a mut TerminalTheme,
pub active_tab_id: &'a mut Option<Id>,
pub search_start: &'a mut bool,
pub search_regex: &'a mut String,
}

impl Widget for TerminalView<'_> {
fn ui(mut self, ui: &mut egui::Ui) -> Response {
let (layout, painter) = ui.allocate_painter(self.size, egui::Sense::click());

let widget_id = self.widget_id;
let mut state = TerminalViewState::load(ui.ctx(), widget_id);
let mut layout_opt = None;

if layout.contains_pointer() {
*self.options.active_tab_id = Some(self.widget_id);
layout.ctx.set_cursor_icon(CursorIcon::Text);
} else {
layout.ctx.set_cursor_icon(CursorIcon::Default);
}
ui.horizontal(|ui| {
let size_p = Vec2::new(self.size.x - InteractiveScrollbar::WIDTH, self.size.y);
let (layout, painter) = ui.allocate_painter(size_p, egui::Sense::click());

if self.options.active_tab_id.is_none() {
self.has_focus = false;
}
if layout.contains_pointer() {
*self.options.active_tab_id = Some(self.widget_id);
layout.ctx.set_cursor_icon(CursorIcon::Text);
} else {
layout.ctx.set_cursor_icon(CursorIcon::Default);
}

// context menu
if let Some(pos) = state.cursor_position {
self.context_menu(pos, &layout, ui);
}
if ui.input(|input_state| input_state.pointer.primary_clicked()) {
state.cursor_position = None;
ui.close_menu();
}
if self.options.active_tab_id.is_none() {
self.has_focus = false;
}

// context menu
if let Some(pos) = state.cursor_position {
self.context_menu(pos, &layout, ui);
}
if ui.input(|input_state| input_state.pointer.primary_clicked()) {
state.cursor_position = None;
ui.close_menu();
}

self.focus(&layout)
.resize(&layout)
.process_input(&mut state, &layout)
.show(&mut state, &layout, &painter);
let mut term = self
.focus(&layout)
.resize(&layout)
.process_input(&mut state, &layout);

state.store(ui.ctx(), widget_id);
layout
let grid = term.term_ctx.terminal.grid_mut();
let total_lines = grid.total_lines() as f32;
let display_offset = grid.display_offset() as f32;
let cell_height = term.term_ctx.size.cell_height as f32;
let total_height = cell_height * total_lines;
let display_offset_pos = display_offset * cell_height;

let mut scrollbar = InteractiveScrollbar::new();
scrollbar.set_first_row_pos(display_offset_pos);
scrollbar.ui(total_height, ui);
if let Some(new_first_row_pos) = scrollbar.new_first_row_pos {
let total_row_pos = new_first_row_pos + state.scrollbar_state.scroll_pixels;
let new_pos = total_row_pos / cell_height;
state.scrollbar_state.scroll_pixels = total_row_pos % cell_height;
let line_diff = new_pos - display_offset;
let line_delta = Scroll::Delta(line_diff.ceil() as i32);
grid.scroll_display(line_delta);
}

if *term.options.search_start {
let mut start_pos = Point::new(Line(0), Column(0));
let regex = term
.term_ctx
.terminal
.inline_search_right(start_pos, term.options.search_regex);
match regex {
Ok(point) => {
println!("point: {}, {}", point.line, term.options.search_regex);
}
Err(_point1) => {
println!("search error: {}", term.options.search_regex);
}
}
}

term.show(&mut state, &layout, &painter);

state.store(ui.ctx(), widget_id);
layout_opt = Some(layout);
});
layout_opt.unwrap()
}
}

Expand Down Expand Up @@ -208,11 +255,14 @@ impl<'a> TerminalView<'a> {
modifiers,
pos,
} => {
if out_of_terminal(pos, layout) {
continue;
}
let new_pos = if out_of_terminal(pos, layout) {
pos.clamp(layout.rect.min, layout.rect.max)
} else {
pos
};

if let Some(action) =
self.button_click(state, layout, button, pos, &modifiers, pressed)
self.button_click(state, layout, button, new_pos, &modifiers, pressed)
{
input_actions.push(action);
}
Expand Down
Loading
Loading