Compare commits

...

14 Commits

Author SHA1 Message Date
nnyyxxxx
eeb501401a
fix confirmation prompt closing via rmb 2024-11-17 01:49:03 -05:00
nnyyxxxx
a8343155e5
change to false
run clippy
2024-11-17 01:49:03 -05:00
nnyyxxxx
692bff1627
w.i.p. left click confirmation 2024-11-17 01:49:03 -05:00
nnyyxxxx
d1baa44833
combine crates 2024-11-17 01:49:03 -05:00
nnyyxxxx
93eb1196e8
make right click close floating windows 2024-11-17 01:49:03 -05:00
nnyyxxxx
6dae016104
add click and drag 2024-11-17 01:49:03 -05:00
nnyyxxxx
684346272d
bring up description on scroll wheel pressdown, bring up script preview on Right Mouse Button pressdown 2024-11-17 01:49:02 -05:00
nnyyxxxx
6e84cb301b
allow selection of search bar 2024-11-17 01:49:02 -05:00
nnyyxxxx
fbfcd5002c
fix subdir bug 2024-11-17 01:49:02 -05:00
nnyyxxxx
961511fb72
improve mouse position calculation 2024-11-17 01:49:02 -05:00
nnyyxxxx
55fed66f0b
w.i.p. concept for more mouse interaction within the tui
run fmt
2024-11-17 01:48:59 -05:00
Jeevitha Kannan K S
fa69885b6c
Use vt100-ctt instead of patching the dep (#952)
Thanks @a-kenji
2024-11-16 15:07:22 -06:00
Adam Perkowski
2dabb934f7
fix: crate cache & versions (#949) 2024-11-14 17:30:48 -06:00
Jeevitha Kannan K S
6d7d8dbc61
Add accidentally deleted preview.tape (#947)
Add labels + Wait 2sec after program ends
2024-11-14 10:46:15 -06:00
10 changed files with 294 additions and 55 deletions

93
.github/preview.tape vendored Normal file
View File

@ -0,0 +1,93 @@
# VHS documentation
#
# Output:
# Output <path>.gif Create a GIF output at the given <path>
# Output <path>.mp4 Create an MP4 output at the given <path>
# Output <path>.webm Create a WebM output at the given <path>
#
# Require:
# Require <string> Ensure a program is on the $PATH to proceed
#
# Settings:
# Set FontSize <number> Set the font size of the terminal
# Set FontFamily <string> Set the font family of the terminal
# Set Height <number> Set the height of the terminal
# Set Width <number> Set the width of the terminal
# Set LetterSpacing <float> Set the font letter spacing (tracking)
# Set LineHeight <float> Set the font line height
# Set LoopOffset <float>% Set the starting frame offset for the GIF loop
# Set Theme <json|string> Set the theme of the terminal
# Set Padding <number> Set the padding of the terminal
# Set Framerate <number> Set the framerate of the recording
# Set PlaybackSpeed <float> Set the playback speed of the recording
# Set MarginFill <file|#000000> Set the file or color the margin will be filled with.
# Set Margin <number> Set the size of the margin. Has no effect if MarginFill isn't set.
# Set BorderRadius <number> Set terminal border radius, in pixels.
# Set WindowBar <string> Set window bar type. (one of: Rings, RingsRight, Colorful, ColorfulRight)
# Set WindowBarSize <number> Set window bar size, in pixels. Default is 40.
# Set TypingSpeed <time> Set the typing speed of the terminal. Default is 50ms.
#
# Sleep:
# Sleep <time> Sleep for a set amount of <time> in seconds
#
# Type:
# Type[@<time>] "<characters>" Type <characters> into the terminal with a
# <time> delay between each character
#
# Keys:
# Escape[@<time>] [number] Press the Escape key
# Backspace[@<time>] [number] Press the Backspace key
# Delete[@<time>] [number] Press the Delete key
# Insert[@<time>] [number] Press the Insert key
# Down[@<time>] [number] Press the Down key
# Enter[@<time>] [number] Press the Enter key
# Space[@<time>] [number] Press the Space key
# Tab[@<time>] [number] Press the Tab key
# Left[@<time>] [number] Press the Left Arrow key
# Right[@<time>] [number] Press the Right Arrow key
# Up[@<time>] [number] Press the Up Arrow key
# Down[@<time>] [number] Press the Down Arrow key
# PageUp[@<time>] [number] Press the Page Up key
# PageDown[@<time>] [number] Press the Page Down key
# Ctrl+<key> Press the Control key + <key> (e.g. Ctrl+C)
#
# Display:
# Hide Hide the subsequent commands from the output
# Show Show the subsequent commands in the output
Output preview.gif
Require linutil
Require sh
Set Shell "bash"
Set FontFamily "JetBrainsMono Nerd Font"
Set FontSize 24
Set Width 1920
Set Height 1080
Sleep 1s
Type "linutil" Sleep 1s Enter
Sleep 2s
Left Sleep 1s
Down Sleep 1s
Down Sleep 1s
Down Sleep 1s
Down Sleep 1s
Right Sleep 1s
Type "/" Sleep 1s
Type@200ms "Full System Cleanup" Sleep 1s Enter
Sleep 1s
Enter Sleep 2s
Type "y" # CONFIRMATION PROMPT
Sleep 15s
Type "y" # SYSTEM CLEANUP PROMPT
Enter
Sleep 4s
Enter
Sleep 2s

View File

@ -67,9 +67,10 @@ jobs:
uses: peter-evans/create-pull-request@v7.0.5
with:
commit-message: Preview for ${{ env.tag_name }}
token: ${{ secrets.PAT_TOKEN }}
branch: feature/preview-${{ env.tag_name }}
title: "Update preview for ${{ env.tag_name }}"
labels: |
documentation
body: |
Automated PR to update preview gif for version ${{ env.tag_name }}
![preview](https://raw.githubusercontent.com/${{ github.repository }}/feature/preview-${{ env.tag_name }}/.github/preview.gif)

32
Cargo.lock generated
View File

@ -381,7 +381,21 @@ checksum = "8e9489c2807c139ffd9c1794f4af0ebe86a828db53ecdc7fea2111d0fed085d1"
[[package]]
name = "linutil_core"
version = "24.9.28"
version = "24.10.31"
dependencies = [
"ego-tree",
"include_dir",
"serde",
"temp-dir",
"toml",
"which",
]
[[package]]
name = "linutil_core"
version = "24.10.31"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2990ea580e635f6700ae19bd0f5fa60c7037799908da476b0c233b9e514c1481"
dependencies = [
"ego-tree",
"include_dir",
@ -393,12 +407,12 @@ dependencies = [
[[package]]
name = "linutil_tui"
version = "24.9.28"
version = "24.10.31"
dependencies = [
"ansi-to-tui",
"anstyle",
"clap",
"linutil_core",
"linutil_core 24.10.31 (registry+https://github.com/rust-lang/crates.io-index)",
"nix 0.29.0",
"oneshot",
"portable-pty",
@ -411,6 +425,7 @@ dependencies = [
"tree-sitter-highlight",
"tui-term",
"unicode-width 0.2.0",
"vt100-ctt",
"zips",
]
@ -1082,7 +1097,6 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "72af159125ce32b02ceaced6cffae6394b0e6b6dfd4dc164a6c59a2db9b3c0b0"
dependencies = [
"ratatui",
"vt100",
]
[[package]]
@ -1133,9 +1147,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"
[[package]]
name = "vt100"
version = "0.15.2"
source = "git+https://github.com/ChrisTitusTech/vt100-rust#e41fb3d8fb5fd01dd2d076c9a25823a31656012f"
name = "vt100-ctt"
version = "0.15.3"
source = "git+https://github.com/ChrisTitusTech/vt100-rust#39136a6232d043d8447afa7d47805b6f6baa09ee"
dependencies = [
"itoa",
"log",
@ -1303,9 +1317,9 @@ checksum = "d135d17ab770252ad95e9a872d365cf3090e3be864a34ab46f48555993efc904"
[[package]]
name = "xtask"
version = "24.9.28"
version = "24.10.31"
dependencies = [
"linutil_core",
"linutil_core 24.10.31",
]
[[package]]

View File

@ -1,6 +1,6 @@
[workspace.package]
license = "MIT"
version = "24.9.28"
version = "24.10.31"
edition = "2021"
[workspace]
@ -8,9 +8,6 @@ members = ["tui", "core", "xtask"]
default-members = ["tui", "core"]
resolver = "2"
[patch.crates-io]
vt100 = { git = "https://github.com/ChrisTitusTech/vt100-rust" }
[profile.release]
opt-level = "z"
debug = false

View File

@ -18,12 +18,12 @@ clap = { version = "4.5.20", features = ["derive", "std"], default-features = fa
oneshot = { version = "0.1.8", features = ["std"], default-features = false }
portable-pty = "0.8.1"
ratatui = { version = "0.29.0", features = ["crossterm"], default-features = false }
tui-term = "0.2.0"
tui-term = { version = "0.2.0", default-features = false }
temp-dir = "0.1.14"
time = { version = "0.3.36", features = ["formatting", "local-offset", "macros"], default-features = false }
unicode-width = { version = "0.2.0", default-features = false }
rand = { version = "0.8.5", optional = true }
linutil_core = { version = "24.9.28", path = "../core" }
linutil_core = { version = "24.10.31" }
tree-sitter-highlight = "0.24.3"
tree-sitter-bash = "0.23.1"
textwrap = { version = "0.16.1", default-features = false }
@ -31,6 +31,7 @@ anstyle = { version = "1.0.8", default-features = false }
ansi-to-tui = { version = "7.0.0", default-features = false }
zips = "0.1.7"
nix = { version = "0.29.0", features = [ "user" ] }
vt100-ctt = { git = "https://github.com/ChrisTitusTech/vt100-rust" }
[[bin]]
name = "linutil"

View File

@ -3,7 +3,7 @@ use std::borrow::Cow;
use crate::{float::FloatContent, hint::Shortcut};
use ratatui::{
crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind},
crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind},
layout::Alignment,
prelude::*,
widgets::{Block, Borders, Clear, List},
@ -87,16 +87,25 @@ impl FloatContent for ConfirmPrompt {
fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool {
match event.kind {
MouseEventKind::Down(MouseButton::Left) => {
self.status = ConfirmStatus::Confirm;
true
}
MouseEventKind::Down(MouseButton::Right) => {
self.status = ConfirmStatus::Abort;
false
}
MouseEventKind::ScrollDown => {
self.scroll_down();
false
}
MouseEventKind::ScrollUp => {
self.scroll_up();
}
_ => {}
}
false
}
_ => false,
}
}
fn handle_key_event(&mut self, key: &KeyEvent) -> bool {
use KeyCode::*;

View File

@ -4,7 +4,7 @@ use ratatui::{
Frame,
};
use crate::hint::Shortcut;
use crate::{event::MouseButton, event::MouseEventKind, hint::Shortcut};
pub trait FloatContent {
fn draw(&mut self, frame: &mut Frame, area: Rect);
@ -54,8 +54,14 @@ impl<Content: FloatContent + ?Sized> Float<Content> {
self.content.draw(frame, popup_area);
}
pub fn handle_mouse_event(&mut self, event: &MouseEvent) {
pub fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool {
match event.kind {
MouseEventKind::Down(MouseButton::Right) => {
self.content.handle_mouse_event(event);
true
}
_ => self.content.handle_mouse_event(event),
}
}
// Returns true if the floating window is finished.

View File

@ -9,7 +9,7 @@ use crate::{float::FloatContent, hint::Shortcut};
use linutil_core::Command;
use ratatui::{
crossterm::event::{KeyCode, KeyEvent, MouseEvent, MouseEventKind},
crossterm::event::{KeyCode, KeyEvent, MouseButton, MouseEvent, MouseEventKind},
layout::Rect,
style::{Style, Stylize},
text::Line,
@ -33,6 +33,8 @@ pub struct FloatingText {
mode_title: String,
wrap_words: bool,
frame_height: usize,
drag_start_y: Option<u16>,
drag_start_scroll: Option<usize>,
}
macro_rules! style {
@ -141,6 +143,8 @@ impl FloatingText {
h_scroll: 0,
wrap_words,
frame_height: 0,
drag_start_y: None,
drag_start_scroll: None,
}
}
@ -165,6 +169,8 @@ impl FloatingText {
v_scroll: 0,
wrap_words: false,
frame_height: 0,
drag_start_y: None,
drag_start_scroll: None,
})
}
@ -206,6 +212,19 @@ impl FloatingText {
};
}
}
fn handle_drag(&mut self, current_y: u16) {
if let (Some(start_y), Some(start_scroll)) = (self.drag_start_y, self.drag_start_scroll) {
let delta = start_y as i32 - current_y as i32;
let new_scroll = start_scroll as i32 + delta;
let max_scroll = self
.wrapped_lines
.len()
.saturating_sub(self.frame_height.saturating_sub(2));
self.v_scroll = new_scroll.clamp(0, max_scroll as i32) as usize;
}
}
}
impl FloatContent for FloatingText {
@ -285,6 +304,17 @@ impl FloatContent for FloatingText {
fn handle_mouse_event(&mut self, event: &MouseEvent) -> bool {
match event.kind {
MouseEventKind::Down(MouseButton::Left) => {
self.drag_start_y = Some(event.row);
self.drag_start_scroll = Some(self.v_scroll);
}
MouseEventKind::Up(MouseButton::Left) => {
self.drag_start_y = None;
self.drag_start_scroll = None;
}
MouseEventKind::Drag(MouseButton::Left) => {
self.handle_drag(event.row);
}
MouseEventKind::ScrollDown => self.scroll_down(),
MouseEventKind::ScrollUp => self.scroll_up(),
MouseEventKind::ScrollLeft => self.scroll_left(),

View File

@ -18,10 +18,9 @@ use std::{
thread::JoinHandle,
};
use time::{macros::format_description, OffsetDateTime};
use tui_term::{
vt100::{self, Screen},
widget::PseudoTerminal,
};
use tui_term::widget::PseudoTerminal;
use vt100_ctt::{Parser, Screen};
pub struct RunningCommand {
/// A buffer to save all the command output (accumulates, until the command exits)
buffer: Arc<Mutex<Vec<u8>>>,
@ -114,7 +113,7 @@ impl FloatContent for RunningCommand {
}
_ => {}
}
true
false
}
/// Handle key events of the running command "window". Returns true when the "window" should be
/// closed
@ -285,7 +284,7 @@ impl RunningCommand {
// Process the buffer with a parser with the current screen size
// We don't actually need to create a new parser every time, but it is so much easier this
// way, and doesn't cost that much
let mut parser = vt100::Parser::new(size.height, size.width, 1000);
let mut parser = Parser::new(size.height, size.width, 1000);
let mutex = self.buffer.lock();
let buffer = mutex.as_ref().unwrap();
parser.process(buffer);

View File

@ -13,7 +13,9 @@ use linutil_core::{ego_tree::NodeId, Config, ListNode, TabList};
#[cfg(feature = "tips")]
use rand::Rng;
use ratatui::{
crossterm::event::{KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseEvent, MouseEventKind},
crossterm::event::{
KeyCode, KeyEvent, KeyEventKind, KeyModifiers, MouseButton, MouseEvent, MouseEventKind,
},
layout::{Alignment, Constraint, Direction, Flex, Layout, Position, Rect},
style::{Style, Stylize},
text::{Line, Span, Text},
@ -481,7 +483,7 @@ impl AppState {
match &mut self.focus {
Focus::FloatingWindow(float) => float.draw(frame, chunks[1]),
Focus::ConfirmationPrompt(prompt) => prompt.draw(frame, chunks[1]),
Focus::ConfirmationPrompt(confirm) => confirm.draw(frame, chunks[1]),
_ => {}
}
@ -493,51 +495,138 @@ impl AppState {
return true;
}
if matches!(self.focus, Focus::TabList | Focus::List) {
match &mut self.focus {
Focus::FloatingWindow(float) => {
if float.handle_mouse_event(event) {
self.focus = Focus::List;
}
return true;
}
Focus::ConfirmationPrompt(confirm) => {
if confirm.handle_mouse_event(event) {
match confirm.content.status {
ConfirmStatus::Abort => {
self.focus = Focus::List;
if !self.multi_select {
self.selected_commands.clear()
} else if let Some(node) = self.get_selected_node() {
if !node.multi_select {
self.selected_commands.retain(|cmd| cmd.name != node.name);
}
}
}
ConfirmStatus::Confirm => self.handle_confirm_command(),
ConfirmStatus::None => {}
}
}
return true;
}
_ => {}
}
if matches!(self.focus, Focus::TabList | Focus::List | Focus::Search) {
let position = Position::new(event.column, event.row);
let mouse_in_tab_list = self.areas.as_ref().unwrap().tab_list.contains(position);
let mouse_in_list = self.areas.as_ref().unwrap().list.contains(position);
let mouse_in_search = if let Some(areas) = &self.areas {
position.y >= areas.list.y
&& position.y < areas.list.y + 3
&& position.x >= areas.list.x
&& position.x < areas.list.x + areas.list.width
} else {
false
};
match event.kind {
MouseEventKind::Moved => {
if mouse_in_list {
self.focus = Focus::List
if mouse_in_search {
if !matches!(self.focus, Focus::Search) {
self.focus = Focus::Search;
self.filter.activate_search();
}
} else if mouse_in_list {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.focus = Focus::List;
if let Some(areas) = &self.areas {
let list_start = areas.list.y + 4;
let relative_y = position.y.saturating_sub(list_start);
let list_len = self.filter.item_list().len();
let adjusted_len = if self.at_root() {
list_len
} else {
list_len + 1
};
if relative_y < adjusted_len as u16 {
self.selection.select(Some(relative_y as usize));
}
}
} else if mouse_in_tab_list {
self.focus = Focus::TabList
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.focus = Focus::TabList;
if let Some(areas) = &self.areas {
let relative_y = position.y.saturating_sub(areas.tab_list.y + 1);
if relative_y < self.tabs.len() as u16 {
self.current_tab.select(Some(relative_y as usize));
self.refresh_tab();
}
}
MouseEventKind::ScrollDown => {
}
}
MouseEventKind::Down(button) => match button {
MouseButton::Left => {
if mouse_in_search {
self.enter_search();
} else if mouse_in_list {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.handle_enter();
} else if mouse_in_tab_list {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.focus = Focus::TabList;
}
}
MouseButton::Right if mouse_in_list => {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.enable_preview();
}
MouseButton::Middle if mouse_in_list => {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
self.enable_description();
}
_ => {}
},
MouseEventKind::ScrollDown | MouseEventKind::ScrollUp => {
if matches!(self.focus, Focus::Search) {
self.exit_search();
}
if mouse_in_tab_list {
if self.current_tab.selected().unwrap() != self.tabs.len() - 1 {
self.current_tab.select_next();
}
self.refresh_tab();
} else if mouse_in_list {
self.selection.select_next()
if event.kind == MouseEventKind::ScrollDown {
self.scroll_down();
} else {
self.scroll_up();
}
}
MouseEventKind::ScrollUp => {
if mouse_in_tab_list {
if self.current_tab.selected().unwrap() != 0 {
self.current_tab.select_previous();
}
self.refresh_tab();
} else if mouse_in_list {
self.selection.select_previous()
}
}
_ => {}
}
}
match &mut self.focus {
Focus::FloatingWindow(float) => {
float.content.handle_mouse_event(event);
}
Focus::ConfirmationPrompt(confirm) => {
confirm.content.handle_mouse_event(event);
}
_ => {}
}
true
}