Skip to content

Commit e531182

Browse files
LoricAndreSkim bot
andauthored
feat: allow negative sizes (closes #1040) (#1043)
* feat: allow negative sizes (closes #1040) * chore: generate completions & manpage --------- Co-authored-by: Skim bot <skim-bot@skim-rs.github.io>
1 parent c28d162 commit e531182

15 files changed

Lines changed: 194 additions & 29 deletions

.github/dependabot.yml

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,5 +12,7 @@ updates:
1212
schedule:
1313
interval: monthly
1414
commit-message:
15-
prefix: chore(ci/deps)
16-
prefix-development: chore(ci/dev-deps)
15+
prefix: chore(deps)
16+
prefix-development: chore(dev-deps)
17+
exclude-paths:
18+
- .github/workflows/release.yml

flake.lock

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

man/man1/sk.1

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ Shorthand for reverse layout
319319
\fB\-\-height\fR \fI<HEIGHT>\fR [default: 100%]
320320
Height of skim\*(Aqs window
321321

322-
Can either be a row count or a percentage
322+
Can either be a row count or a percentage A negative row count will use term height \- value as height
323323
.TP
324324
\fB\-\-no\-height\fR
325325
Disable height (force full screen)
@@ -506,13 +506,13 @@ ls \-l | sk \-\-preview="echo user={3} when={\-4..\-2}; cat {\-1}" \-\-header\-l
506506
\fB\-\-preview\-window\fR \fI<PREVIEW_WINDOW>\fR [default: right:50%]
507507
Preview window layout
508508

509-
Format: [up|down|left|right][:SIZE[%]][:hidden][:[no]wrap][:[no]pty][:+SCROLL[\-OFFSET]]
509+
Format: [up|down|left|right][:SIZE][:hidden][:[no]wrap][:[no]pty][:+SCROLL[\-OFFSET]]
510510

511511
Determine the layout of the preview window. If the argument ends with: hidden, the preview window will be hidden by default until toggle\-preview action is triggered. Long lines are truncated by default. Line wrap can be enabled with :wrap flag. For more interactive commands or previews that draw complex interfaces, the preview can use a PTY with the :pty flag.
512512

513513
Note: the preview will run in a PTY (interactive session) on linux and when wrap is unset
514514

515-
If size is given as 0, preview window will not be visible, but sk will still execute the command in the background.
515+
SIZE can be either: \- 0, which will hide the preview window \- A positive size (eg 20) \- A percentage of the total size (eg 50%) \- A negative size, which will set the size of everything but the preview to that value
516516

517517
+SCROLL[\-OFFSET] determines the initial scroll offset of the preview window. SCROLL can be either a numeric integer or a single\-field index expression that refers to a numeric integer. The optional \-OFFSET part is for adjusting the base offset so that you can see the text above it. It should be given as a numeric integer (\-INTEGER), or as a denom‐ inator form (\-/INTEGER) for specifying a fraction of the preview window height.
518518

src/options.rs

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -405,7 +405,11 @@ pub struct SkimOptions {
405405
/// Height of skim's window
406406
///
407407
/// Can either be a row count or a percentage
408-
#[cfg_attr(feature = "cli", arg(long, default_value = "100%", help_heading = "Layout"))]
408+
/// A negative row count will use `term height` - `value` as height
409+
#[cfg_attr(
410+
feature = "cli",
411+
arg(long, default_value = "100%", help_heading = "Layout", allow_hyphen_values = true)
412+
)]
409413
pub height: String,
410414

411415
/// Disable height (force full screen)
@@ -630,7 +634,7 @@ pub struct SkimOptions {
630634

631635
/// Preview window layout
632636
///
633-
/// Format: [up|down|left|right][:SIZE[%]][:hidden][:[no]wrap][:[no]pty][:+SCROLL[-OFFSET]]
637+
/// Format: [up|down|left|right][:SIZE][:hidden][:[no]wrap][:[no]pty][:+SCROLL[-OFFSET]]
634638
///
635639
/// Determine the layout of the preview window. If the argument ends with: hidden, the preview window will be hidden by
636640
/// default until toggle-preview action is triggered. Long lines are truncated by default.
@@ -639,7 +643,11 @@ pub struct SkimOptions {
639643
///
640644
/// Note: the preview will run in a PTY (interactive session) on linux and when `wrap` is unset
641645
///
642-
/// If size is given as 0, preview window will not be visible, but sk will still execute the command in the background.
646+
/// SIZE can be either:
647+
/// - `0`, which will hide the preview window
648+
/// - A positive size (eg `20`)
649+
/// - A percentage of the total size (eg `50%`)
650+
/// - A negative size, which will set the size of everything but the preview to that value
643651
///
644652
/// +SCROLL[-OFFSET] determines the initial scroll offset of the preview window. SCROLL can be either a numeric integer
645653
/// or a single-field index expression that refers to a numeric integer. The optional -OFFSET part is for adjusting the

src/popup/zellij.rs

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ pub(super) struct ZellijPopup {
1616
fn middle_coord(size: Size, var: &str) -> Size {
1717
match size {
1818
Size::Percent(p) => Size::Percent(100u16.saturating_sub(p) / 2),
19-
Size::Fixed(cols) => Size::Fixed(
19+
Size::Fixed(cells) => Size::Fixed(
2020
std::env::var(var)
2121
.map(|s| s.parse().unwrap_or(80))
2222
.unwrap_or(80u16)
23-
.saturating_sub(cols)
23+
.saturating_sub(cells)
2424
/ 2,
2525
),
26+
Size::Neg(cells) => Size::Fixed(cells / 2),
2627
}
2728
}
2829

@@ -35,6 +36,7 @@ fn align_end_coord(size: Size, var: &str) -> Size {
3536
.unwrap_or(80u16)
3637
.saturating_sub(cols),
3738
),
39+
Size::Neg(cells) => Size::Fixed(cells),
3840
}
3941
}
4042

src/tui/app.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -406,6 +406,7 @@ impl App {
406406
}
407407
}
408408

409+
#[allow(clippy::too_many_lines)]
409410
fn run_preview<B: Backend>(&mut self, tui: &mut Tui<B>) -> Result<()>
410411
where
411412
B::Error: Send + Sync + 'static,
@@ -468,12 +469,14 @@ impl App {
468469
// Apply position offsets
469470
let v_scroll = match preview_position.v_scroll {
470471
crate::tui::Size::Fixed(n) => n,
472+
crate::tui::Size::Neg(n) => self.preview.rows.saturating_sub(n),
471473
crate::tui::Size::Percent(p) => {
472474
u16::try_from(u32::from(self.preview.rows) * u32::from(p) / 100).unwrap_or(u16::MAX)
473475
}
474476
};
475477
let v_offset = match preview_position.v_offset {
476478
crate::tui::Size::Fixed(n) => n,
479+
crate::tui::Size::Neg(n) => self.preview.rows.saturating_sub(n),
477480
crate::tui::Size::Percent(p) => {
478481
u16::try_from(u32::from(self.preview.rows) * u32::from(p) / 100).unwrap_or(u16::MAX)
479482
}
@@ -483,12 +486,14 @@ impl App {
483486

484487
let h_scroll = match preview_position.h_scroll {
485488
crate::tui::Size::Fixed(n) => n,
489+
crate::tui::Size::Neg(n) => self.preview.cols.saturating_sub(n),
486490
crate::tui::Size::Percent(p) => {
487491
u16::try_from(u32::from(self.preview.cols) * u32::from(p) / 100).unwrap_or(u16::MAX)
488492
}
489493
};
490494
let h_offset = match preview_position.h_offset {
491495
crate::tui::Size::Fixed(n) => n,
496+
crate::tui::Size::Neg(n) => self.preview.cols.saturating_sub(n),
492497
crate::tui::Size::Percent(p) => {
493498
u16::try_from(u32::from(self.preview.cols) * u32::from(p) / 100).unwrap_or(u16::MAX)
494499
}

src/tui/backend.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ where
7575
Size::Percent(100) => None,
7676
Size::Fixed(lines) => Some(lines),
7777
Size::Percent(p) => Some(term_height * p / 100),
78+
Size::Neg(lines) => Some(term_height.saturating_sub(lines)),
7879
};
7980

8081
let viewport = if let Some(mut height) = lines {

src/tui/layout.rs

Lines changed: 12 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -92,22 +92,23 @@ impl LayoutTemplate {
9292
};
9393

9494
// Preview placement and layout.
95-
let preview_visible =
96-
(options.preview.is_some() || options.preview_fn.is_some()) && !options.preview_window.hidden;
95+
let preview_visible = (options.preview.is_some() || options.preview_fn.is_some())
96+
&& !options.preview_window.hidden
97+
&& !matches!(options.preview_window.size, Size::Fixed(0));
9798

9899
let (preview_placement, preview_layout) = if preview_visible {
99-
let pc = size_to_constraint(options.preview_window.size);
100+
let (preview_c, rest_c) = size_to_constraint(options.preview_window.size);
100101
let placement = match options.preview_window.direction {
101102
Direction::Left => PreviewPlacement::Left,
102103
Direction::Right => PreviewPlacement::Right,
103104
Direction::Up => PreviewPlacement::Up,
104105
Direction::Down => PreviewPlacement::Down,
105106
};
106107
let layout = match placement {
107-
PreviewPlacement::Left => Layout::new(RatatuiDirection::Horizontal, [pc, Constraint::Fill(1)]),
108-
PreviewPlacement::Right => Layout::new(RatatuiDirection::Horizontal, [Constraint::Fill(1), pc]),
109-
PreviewPlacement::Up => Layout::new(RatatuiDirection::Vertical, [pc, Constraint::Fill(1)]),
110-
PreviewPlacement::Down => Layout::new(RatatuiDirection::Vertical, [Constraint::Fill(1), pc]),
108+
PreviewPlacement::Left => Layout::new(RatatuiDirection::Horizontal, [preview_c, rest_c]),
109+
PreviewPlacement::Right => Layout::new(RatatuiDirection::Horizontal, [rest_c, preview_c]),
110+
PreviewPlacement::Up => Layout::new(RatatuiDirection::Vertical, [preview_c, rest_c]),
111+
PreviewPlacement::Down => Layout::new(RatatuiDirection::Vertical, [rest_c, preview_c]),
111112
PreviewPlacement::None => unreachable!(),
112113
};
113114
(placement, Some(layout))
@@ -237,10 +238,11 @@ impl AppLayout {
237238
// Helper
238239
// ---------------------------------------------------------------------------
239240

240-
fn size_to_constraint(size: Size) -> Constraint {
241+
fn size_to_constraint(size: Size) -> (Constraint, Constraint) {
241242
match size {
242-
Size::Fixed(n) => Constraint::Length(n),
243-
Size::Percent(p) => Constraint::Percentage(p),
243+
Size::Fixed(n) => (Constraint::Length(n), Constraint::Fill(1)),
244+
Size::Percent(p) => (Constraint::Percentage(p), Constraint::Fill(1)),
245+
Size::Neg(n) => (Constraint::Fill(1), Constraint::Length(n)),
244246
}
245247
}
246248

src/tui/mod.rs

Lines changed: 9 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -46,6 +46,8 @@ pub enum Size {
4646
Percent(u16),
4747
/// Fixed size in terminal cells
4848
Fixed(u16),
49+
/// Negative size is substracted from term height
50+
Neg(u16),
4951
}
5052

5153
/// Direction for movement or layout
@@ -99,6 +101,11 @@ impl TryFrom<&str> for Size {
99101
return Err(SizeParseError::InvalidPercent(percent));
100102
}
101103
Ok(Self::Percent(percent))
104+
} else if let Some(neg) = value.strip_prefix('-') {
105+
Ok(Self::Neg(
106+
neg.parse::<u16>()
107+
.map_err(|e| SizeParseError::ParseError(value.to_string(), e))?,
108+
))
102109
} else {
103110
Ok(Self::Fixed(
104111
value
@@ -120,6 +127,7 @@ impl std::fmt::Display for Size {
120127
match self {
121128
Self::Percent(p) => f.write_fmt(format_args!("{p}%")),
122129
Self::Fixed(s) => f.write_fmt(format_args!("{s}")),
130+
Self::Neg(s) => f.write_fmt(format_args!("-{s}")),
123131
}
124132
}
125133
}
@@ -183,11 +191,7 @@ mod size_test {
183191
}
184192
#[test]
185193
fn fixed_neg() {
186-
let SizeParseError::ParseError(err_value, internal_error) = Size::try_from("-10").unwrap_err() else {
187-
panic!();
188-
};
189-
assert_eq!(internal_error.kind(), &IntErrorKind::InvalidDigit);
190-
assert_eq!(err_value, String::from("-10"));
194+
assert_eq!(Size::try_from("-10"), Ok(Size::Neg(10)));
191195
}
192196
#[test]
193197
fn percent_neg() {

src/tui/preview.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -101,6 +101,10 @@ impl Preview {
101101
// Result is at most dimension (a u16), so truncation cannot occur.
102102
u16::try_from(u32::from(dimension) * u32::from(p) / 100).unwrap_or(u16::MAX)
103103
}
104+
super::Size::Neg(n) => {
105+
let dimension = if is_vertical { self.rows } else { self.cols };
106+
dimension.saturating_sub(n)
107+
}
104108
}
105109
}
106110

0 commit comments

Comments
 (0)