[+] Live filter preset selection (#482)
* [+] Live filter preset selection * [F] Resolve Copilot issues
This commit is contained in:
Generated
+35
@@ -115,8 +115,11 @@ dependencies = [
|
|||||||
"bitflags",
|
"bitflags",
|
||||||
"crossterm_winapi",
|
"crossterm_winapi",
|
||||||
"document-features",
|
"document-features",
|
||||||
|
"mio",
|
||||||
"parking_lot",
|
"parking_lot",
|
||||||
"rustix",
|
"rustix",
|
||||||
|
"signal-hook",
|
||||||
|
"signal-hook-mio",
|
||||||
"winapi",
|
"winapi",
|
||||||
]
|
]
|
||||||
|
|
||||||
@@ -411,6 +414,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index"
|
|||||||
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
checksum = "78bed444cc8a2160f01cbcf811ef18cac863ad68ae8ca62092e8db51d51c761c"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"libc",
|
"libc",
|
||||||
|
"log",
|
||||||
"wasi",
|
"wasi",
|
||||||
"windows-sys 0.59.0",
|
"windows-sys 0.59.0",
|
||||||
]
|
]
|
||||||
@@ -703,6 +707,37 @@ version = "1.1.0"
|
|||||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
checksum = "24188a676b6ae68c3b2cb3a01be17fbf7240ce009799bb56d5b1409051e78fde"
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook"
|
||||||
|
version = "0.3.18"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "d881a16cf4426aa584979d30bd82cb33429027e42122b169753d6ef1085ed6e2"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"signal-hook-registry",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-mio"
|
||||||
|
version = "0.2.5"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "b75a19a7a740b25bc7944bdee6172368f988763b744e3d4dfe753f6b4ece40cc"
|
||||||
|
dependencies = [
|
||||||
|
"libc",
|
||||||
|
"mio",
|
||||||
|
"signal-hook",
|
||||||
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "signal-hook-registry"
|
||||||
|
version = "1.4.8"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
checksum = "c4db69cba1110affc0e9f7bcd48bbf87b3f4fc7c61fc9155afd4c469eb3d6c1b"
|
||||||
|
dependencies = [
|
||||||
|
"errno",
|
||||||
|
"libc",
|
||||||
|
]
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "smallvec"
|
name = "smallvec"
|
||||||
version = "1.15.1"
|
version = "1.15.1"
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ ansi_colours = { workspace = true, features = [] }
|
|||||||
anstream = { workspace = true, features = [], optional = true }
|
anstream = { workspace = true, features = [], optional = true }
|
||||||
anyhow = { workspace = true, features = ["std"] }
|
anyhow = { workspace = true, features = ["std"] }
|
||||||
bpaf = { workspace = true, features = [] }
|
bpaf = { workspace = true, features = [] }
|
||||||
crossterm = { workspace = true, features = [] }
|
crossterm = { workspace = true, features = ["events"] }
|
||||||
deranged = { workspace = true, features = ["serde", "std"] }
|
deranged = { workspace = true, features = ["serde", "std"] }
|
||||||
directories = { workspace = true, features = [] }
|
directories = { workspace = true, features = [] }
|
||||||
enterpolation = { workspace = true, features = ["bspline", "std"] }
|
enterpolation = { workspace = true, features = ["bspline", "std"] }
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ use std::borrow::Cow;
|
|||||||
use std::cmp;
|
use std::cmp;
|
||||||
use std::fmt::Write as _;
|
use std::fmt::Write as _;
|
||||||
use std::fs::{self, File};
|
use std::fs::{self, File};
|
||||||
use std::io::{self, IsTerminal as _, Read as _};
|
use std::io::{self, IsTerminal as _, Read as _, Write};
|
||||||
use std::iter;
|
use std::iter;
|
||||||
use std::iter::zip;
|
use std::iter::zip;
|
||||||
use std::num::NonZeroU8;
|
use std::num::NonZeroU8;
|
||||||
@@ -10,6 +10,8 @@ use std::path::{Path, PathBuf};
|
|||||||
|
|
||||||
use aho_corasick::AhoCorasick;
|
use aho_corasick::AhoCorasick;
|
||||||
use anyhow::{Context as _, Result};
|
use anyhow::{Context as _, Result};
|
||||||
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use deranged::RangedU8;
|
use deranged::RangedU8;
|
||||||
use enterpolation::bspline::BSpline;
|
use enterpolation::bspline::BSpline;
|
||||||
use enterpolation::{Curve as _, Generator as _};
|
use enterpolation::{Curve as _, Generator as _};
|
||||||
@@ -44,7 +46,7 @@ use tracing::debug;
|
|||||||
|
|
||||||
fn main() -> Result<()> {
|
fn main() -> Result<()> {
|
||||||
add_pkg_path().expect("failed to add pkg path");
|
add_pkg_path().expect("failed to add pkg path");
|
||||||
|
|
||||||
#[cfg(windows)]
|
#[cfg(windows)]
|
||||||
if let Err(err) = enable_ansi_support::enable_ansi_support() {
|
if let Err(err) = enable_ansi_support::enable_ansi_support() {
|
||||||
debug!(%err, "could not enable ANSI escape code support");
|
debug!(%err, "could not enable ANSI escape code support");
|
||||||
@@ -453,6 +455,40 @@ fn create_config(
|
|||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// 3. Choose preset
|
// 3. Choose preset
|
||||||
|
|
||||||
|
struct RawModeGuard {
|
||||||
|
enabled: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl RawModeGuard {
|
||||||
|
fn new() -> Result<Self> {
|
||||||
|
Ok(Self { enabled: false })
|
||||||
|
}
|
||||||
|
|
||||||
|
fn enable(&mut self) -> Result<()> {
|
||||||
|
if !self.enabled {
|
||||||
|
enable_raw_mode().context("failed to enable terminal raw mode")?;
|
||||||
|
self.enabled = true;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn disable(&mut self) -> Result<()> {
|
||||||
|
if self.enabled {
|
||||||
|
disable_raw_mode().context("failed to disable terminal raw mode")?;
|
||||||
|
self.enabled = false;
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for RawModeGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
if self.enabled {
|
||||||
|
let _ = disable_raw_mode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Create flag lines
|
// Create flag lines
|
||||||
let mut flags = Vec::with_capacity(Preset::COUNT);
|
let mut flags = Vec::with_capacity(Preset::COUNT);
|
||||||
let spacing = {
|
let spacing = {
|
||||||
@@ -479,7 +515,11 @@ fn create_config(
|
|||||||
name = preset.as_ref(),
|
name = preset.as_ref(),
|
||||||
spacing = usize::from(spacing)
|
spacing = usize::from(spacing)
|
||||||
);
|
);
|
||||||
flags.push([name, flag.clone(), flag.clone(), flag]);
|
flags.push((
|
||||||
|
preset.clone(),
|
||||||
|
[name, flag.clone(), flag.clone(), flag],
|
||||||
|
preset.as_ref().to_ascii_lowercase(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Calculate flags per row
|
// Calculate flags per row
|
||||||
@@ -489,34 +529,28 @@ fn create_config(
|
|||||||
let rows_per_page = (term_h.saturating_sub(13) / 5).clamp(1, u8::MAX.into()) as u8;
|
let rows_per_page = (term_h.saturating_sub(13) / 5).clamp(1, u8::MAX.into()) as u8;
|
||||||
(flags_per_row, rows_per_page)
|
(flags_per_row, rows_per_page)
|
||||||
};
|
};
|
||||||
let num_pages = (Preset::COUNT.div_ceil(flags_per_row as usize * rows_per_page as usize)).clamp(0, u8::MAX.into()) as u8;
|
let flags_per_page = usize::from(flags_per_row) * usize::from(rows_per_page);
|
||||||
|
|
||||||
// Create pages
|
fn filter_flag_indices(query: &str, flags: &[(Preset, [String; 4], String)]) -> Vec<usize> {
|
||||||
let mut pages = Vec::with_capacity(usize::from(num_pages));
|
if query.is_empty() {
|
||||||
for flags in flags.chunks(usize::from(
|
return (0..flags.len()).collect();
|
||||||
u16::from(flags_per_row)
|
|
||||||
.checked_mul(u16::from(rows_per_page))
|
|
||||||
.unwrap(),
|
|
||||||
)) {
|
|
||||||
let mut page = Vec::with_capacity(usize::from(rows_per_page));
|
|
||||||
for flags in flags.chunks(usize::from(flags_per_row)) {
|
|
||||||
page.push(flags);
|
|
||||||
}
|
}
|
||||||
pages.push(page);
|
|
||||||
|
let mut matched = flags
|
||||||
|
.iter()
|
||||||
|
.enumerate()
|
||||||
|
.filter_map(|(idx, (_, _, preset_name))| {
|
||||||
|
let position = preset_name.find(query)?;
|
||||||
|
Some((idx, preset_name.starts_with(query), position))
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
|
// Prefix matches are shown first, then other substring matches ordered by earliest index.
|
||||||
|
matched.sort_by_key(|&(idx, is_prefix, position)| (!is_prefix, position, idx));
|
||||||
|
matched.into_iter().map(|(idx, _, _)| idx).collect()
|
||||||
}
|
}
|
||||||
|
|
||||||
let print_flag_page = |page, page_num: u8| -> Result<()> {
|
fn print_flag_row(row: &[&[String; 4]], color_mode: AnsiMode) -> Result<()> {
|
||||||
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
|
||||||
print_title_prompt(option_counter, "Let's choose a flag!");
|
|
||||||
println!("Available flag presets:\nPage: {page_num} of {num_pages}\n", page_num = page_num + 1);
|
|
||||||
for &row in page {
|
|
||||||
print_flag_row(row, color_mode).context("failed to print flag row")?;
|
|
||||||
}
|
|
||||||
println!();
|
|
||||||
Ok(())
|
|
||||||
};
|
|
||||||
|
|
||||||
fn print_flag_row(row: &[[String; 4]], color_mode: AnsiMode) -> Result<()> {
|
|
||||||
for i in 0..4 {
|
for i in 0..4 {
|
||||||
let mut line = Vec::new();
|
let mut line = Vec::new();
|
||||||
for flag in row {
|
for flag in row {
|
||||||
@@ -539,32 +573,138 @@ fn create_config(
|
|||||||
)
|
)
|
||||||
.expect("coloring text with default preset should not fail");
|
.expect("coloring text with default preset should not fail");
|
||||||
|
|
||||||
|
let print_flag_page = |filtered_indices: &[usize],
|
||||||
|
page_num: usize,
|
||||||
|
filter: &str,
|
||||||
|
hint: Option<&str>|
|
||||||
|
-> Result<()> {
|
||||||
|
let num_pages = filtered_indices.len().div_ceil(flags_per_page).max(1);
|
||||||
|
clear_screen(Some(&title), color_mode, debug_mode).context("failed to clear screen")?;
|
||||||
|
print_title_prompt(option_counter, "Let's choose a flag!");
|
||||||
|
println!(
|
||||||
|
"Available flag presets:\nPage: {page} of {num_pages}\n",
|
||||||
|
page = page_num + 1
|
||||||
|
);
|
||||||
|
|
||||||
|
let start = page_num * flags_per_page;
|
||||||
|
let end = (start + flags_per_page).min(filtered_indices.len());
|
||||||
|
let mut visible_rows: usize = 0;
|
||||||
|
if start >= end {
|
||||||
|
println!("No presets matched this filter.");
|
||||||
|
} else {
|
||||||
|
for row in filtered_indices[start..end].chunks(usize::from(flags_per_row)) {
|
||||||
|
let row = row
|
||||||
|
.iter()
|
||||||
|
.map(|&idx| &flags[idx].1)
|
||||||
|
.collect::<Vec<&[String; 4]>>();
|
||||||
|
print_flag_row(&row, color_mode).context("failed to print flag row")?;
|
||||||
|
visible_rows += 1;
|
||||||
|
}
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
// Keep the prompt anchored by reserving a full page worth of flag rows.
|
||||||
|
for _ in visible_rows..usize::from(rows_per_page) {
|
||||||
|
for _ in 0..5 {
|
||||||
|
println!();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
println!(
|
||||||
|
"Use the up/down arrow keys to go to the previous/next page. Type to filter and press Enter to select."
|
||||||
|
);
|
||||||
|
printc(
|
||||||
|
format!(
|
||||||
|
"Which {preset_default_colored} do you want to use? (default: {})",
|
||||||
|
Preset::Rainbow.as_ref()
|
||||||
|
),
|
||||||
|
color_mode,
|
||||||
|
)
|
||||||
|
.context("failed to print preset prompt")?;
|
||||||
|
print!("> {filter}");
|
||||||
|
io::stdout().flush().context("failed to flush preset prompt")?;
|
||||||
|
|
||||||
|
if let Some(hint) = hint {
|
||||||
|
println!("{hint}");
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
};
|
||||||
|
|
||||||
let preset: Preset;
|
let preset: Preset;
|
||||||
let color_profile;
|
let color_profile;
|
||||||
|
|
||||||
let mut page: u8 = 0;
|
let mut page: usize = 0;
|
||||||
|
let mut filter = String::new();
|
||||||
|
let mut hint: Option<&str> = None;
|
||||||
|
let mut raw_mode = RawModeGuard::new().context("failed to initialize raw input mode")?;
|
||||||
loop {
|
loop {
|
||||||
print_flag_page(&pages[usize::from(page)], page).context("failed to print flag page")?;
|
raw_mode
|
||||||
|
.disable()
|
||||||
|
.context("failed to disable raw mode for rendering")?;
|
||||||
|
let filter_lower = filter.to_ascii_lowercase();
|
||||||
|
let filtered_indices = filter_flag_indices(&filter_lower, &flags);
|
||||||
|
let num_pages = filtered_indices.len().div_ceil(flags_per_page).max(1);
|
||||||
|
page = page.min(num_pages - 1);
|
||||||
|
|
||||||
let mut opts: Vec<&str> = <Preset as VariantNames>::VARIANTS.into();
|
print_flag_page(&filtered_indices, page, &filter, hint)
|
||||||
opts.extend(["next", "n", "prev", "p"]);
|
.context("failed to print flag page")?;
|
||||||
|
hint = None;
|
||||||
|
|
||||||
|
raw_mode
|
||||||
|
.enable()
|
||||||
|
.context("failed to enable raw mode for key input")?;
|
||||||
|
let event = event::read().context("failed to read keyboard event")?;
|
||||||
|
let Event::Key(key) = event else {
|
||||||
|
continue;
|
||||||
|
};
|
||||||
|
if !matches!(key.kind, KeyEventKind::Press | KeyEventKind::Repeat) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Enter => {
|
||||||
|
let selection = flags
|
||||||
|
.iter()
|
||||||
|
.find(|(_, _, name)| *name == filter_lower)
|
||||||
|
.map(|(preset, _, _)| *preset)
|
||||||
|
.or_else(|| filtered_indices.first().map(|&idx| flags[idx].0));
|
||||||
|
preset = selection.unwrap_or(Preset::Rainbow);
|
||||||
|
break;
|
||||||
|
},
|
||||||
|
KeyCode::Up => {
|
||||||
|
page = (page + num_pages - 1) % num_pages;
|
||||||
|
},
|
||||||
|
KeyCode::Down => {
|
||||||
|
page = (page + 1) % num_pages;
|
||||||
|
},
|
||||||
|
KeyCode::Backspace => {
|
||||||
|
filter.pop();
|
||||||
|
page = 0;
|
||||||
|
},
|
||||||
|
KeyCode::Esc => {
|
||||||
|
filter.clear();
|
||||||
|
page = 0;
|
||||||
|
},
|
||||||
|
KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => {
|
||||||
|
raw_mode
|
||||||
|
.disable()
|
||||||
|
.context("failed to disable raw mode before interrupting")?;
|
||||||
|
println!();
|
||||||
|
return Err(anyhow::anyhow!("interrupted by user"));
|
||||||
|
},
|
||||||
|
KeyCode::Char(c)
|
||||||
|
if !key.modifiers.contains(KeyModifiers::CONTROL)
|
||||||
|
&& !key.modifiers.contains(KeyModifiers::ALT) =>
|
||||||
|
{
|
||||||
|
filter.push(c);
|
||||||
|
page = 0;
|
||||||
|
},
|
||||||
|
_ => {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
raw_mode
|
||||||
|
.disable()
|
||||||
|
.context("failed to disable raw mode after preset selection")?;
|
||||||
|
|
||||||
println!("Enter '[n]ext' to go to the next page and '[p]rev' to go to the previous page.");
|
|
||||||
let selection = literal_input(
|
|
||||||
format!("Which {preset_default_colored} do you want to use? "),
|
|
||||||
&opts[..],
|
|
||||||
Preset::Rainbow.as_ref(),
|
|
||||||
false,
|
|
||||||
color_mode,
|
|
||||||
)
|
|
||||||
.context("failed to ask for choice input")
|
|
||||||
.context("failed to select preset")?;
|
|
||||||
if selection == "next" || selection == "n" {
|
|
||||||
page = (page + 1) % num_pages;
|
|
||||||
} else if selection == "prev" || selection == "p" {
|
|
||||||
page = (page + num_pages - 1) % num_pages;
|
|
||||||
} else {
|
|
||||||
preset = selection.parse().expect("selected preset should be valid");
|
|
||||||
debug!(?preset, "selected preset");
|
debug!(?preset, "selected preset");
|
||||||
color_profile = preset.color_profile();
|
color_profile = preset.color_profile();
|
||||||
update_title(
|
update_title(
|
||||||
@@ -581,9 +721,14 @@ fn create_config(
|
|||||||
)
|
)
|
||||||
.expect("coloring text with selected preset should not fail"),
|
.expect("coloring text with selected preset should not fail"),
|
||||||
);
|
);
|
||||||
break;
|
printc(
|
||||||
}
|
format!(
|
||||||
}
|
"Which {preset_default_colored} do you want to use? {}\n",
|
||||||
|
preset.as_ref()
|
||||||
|
),
|
||||||
|
color_mode,
|
||||||
|
)
|
||||||
|
.context("failed to print preset selection summary")?;
|
||||||
|
|
||||||
//////////////////////////////
|
//////////////////////////////
|
||||||
// 4. Dim/lighten colors
|
// 4. Dim/lighten colors
|
||||||
@@ -742,26 +887,26 @@ fn create_config(
|
|||||||
} else {
|
} else {
|
||||||
detected_dst.unwrap()
|
detected_dst.unwrap()
|
||||||
};
|
};
|
||||||
|
|
||||||
let running_dst_sml = if Distro::detect(&detected_dst_small_fmt).is_some() {
|
let running_dst_sml = if Distro::detect(&detected_dst_small_fmt).is_some() {
|
||||||
detected_dst_small_fmt
|
detected_dst_small_fmt
|
||||||
} else {
|
} else {
|
||||||
"".to_string()
|
"".to_string()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
||||||
// load ascii
|
// load ascii
|
||||||
let small_asc = get_distro_ascii(Some(&running_dst_sml), backend).context("failed to get distro ascii")?;
|
let small_asc = get_distro_ascii(Some(&running_dst_sml), backend).context("failed to get distro ascii")?;
|
||||||
let small_asc = small_asc.to_normalized().context("failed to normalize ascii")?;
|
let small_asc = small_asc.to_normalized().context("failed to normalize ascii")?;
|
||||||
|
|
||||||
let mut asc = asc;
|
let mut asc = asc;
|
||||||
let mut logo_chosen: Option<String> = distro.cloned();
|
let mut logo_chosen: Option<String> = distro.cloned();
|
||||||
|
|
||||||
if small_asc.lines != asc.lines && running_dst_sml != "" {
|
if small_asc.lines != asc.lines && running_dst_sml != "" {
|
||||||
let ds_arrangements = [
|
let ds_arrangements = [
|
||||||
("Default", asc.clone()),
|
("Default", asc.clone()),
|
||||||
("Small", small_asc.clone())
|
("Small", small_asc.clone())
|
||||||
];
|
];
|
||||||
|
|
||||||
let arrangements: IndexMap<Cow<str>, NormalizedAsciiArt> =
|
let arrangements: IndexMap<Cow<str>, NormalizedAsciiArt> =
|
||||||
ds_arrangements.map(|(k, a)| (k.into(), a)).into();
|
ds_arrangements.map(|(k, a)| (k.into(), a)).into();
|
||||||
@@ -798,15 +943,15 @@ fn create_config(
|
|||||||
|
|
||||||
// prints small logo w/ big logo
|
// prints small logo w/ big logo
|
||||||
for row in &asciis.into_iter().chunks(usize::from(ascii_per_row)) {
|
for row in &asciis.into_iter().chunks(usize::from(ascii_per_row)) {
|
||||||
|
|
||||||
let row: Vec<Vec<String>> = row.collect();
|
let row: Vec<Vec<String>> = row.collect();
|
||||||
|
|
||||||
for i in 0..usize::from(asc.h).checked_add(1).unwrap() {
|
for i in 0..usize::from(asc.h).checked_add(1).unwrap() {
|
||||||
let mut line = Vec::new();
|
let mut line = Vec::new();
|
||||||
for lines in &row {
|
for lines in &row {
|
||||||
line.push(&*lines[i]);
|
line.push(&*lines[i]);
|
||||||
}
|
}
|
||||||
printc(line.join(" "), color_mode).context("failed to print ascii line")?;
|
printc(line.join(" "), color_mode).context("failed to print ascii line")?;
|
||||||
}
|
}
|
||||||
|
|
||||||
println!();
|
println!();
|
||||||
@@ -817,7 +962,7 @@ fn create_config(
|
|||||||
let choice = literal_input("Your choice?", &opts[..], "default", true, color_mode)
|
let choice = literal_input("Your choice?", &opts[..], "default", true, color_mode)
|
||||||
.context("failed to ask for choice input")
|
.context("failed to ask for choice input")
|
||||||
.context("failed to select logo type").context("failed to ask for choice input")?;
|
.context("failed to select logo type").context("failed to ask for choice input")?;
|
||||||
|
|
||||||
if choice.to_lowercase() == "small" {
|
if choice.to_lowercase() == "small" {
|
||||||
logo_chosen = Some(running_dst_sml);
|
logo_chosen = Some(running_dst_sml);
|
||||||
asc = small_asc;
|
asc = small_asc;
|
||||||
@@ -946,7 +1091,7 @@ fn create_config(
|
|||||||
// Save choice
|
// Save choice
|
||||||
color_align = if choice == "horizontal" { ColorAlignment::Horizontal }
|
color_align = if choice == "horizontal" { ColorAlignment::Horizontal }
|
||||||
else if choice == "vertical" { ColorAlignment::Vertical }
|
else if choice == "vertical" { ColorAlignment::Vertical }
|
||||||
else {
|
else {
|
||||||
arrangements.into_iter()
|
arrangements.into_iter()
|
||||||
.find_map(|(k, ca)| {
|
.find_map(|(k, ca)| {
|
||||||
if k.to_lowercase() == choice {
|
if k.to_lowercase() == choice {
|
||||||
|
|||||||
+117
-44
@@ -7,6 +7,7 @@ import importlib.util
|
|||||||
import json
|
import json
|
||||||
import os
|
import os
|
||||||
import random
|
import random
|
||||||
|
import sys
|
||||||
import traceback
|
import traceback
|
||||||
from itertools import permutations, islice
|
from itertools import permutations, islice
|
||||||
from math import ceil
|
from math import ceil
|
||||||
@@ -126,62 +127,134 @@ def create_config() -> Config:
|
|||||||
##############################
|
##############################
|
||||||
# 3. Choose preset
|
# 3. Choose preset
|
||||||
# Create flags = [[lines]]
|
# Create flags = [[lines]]
|
||||||
flags = []
|
flag_entries = []
|
||||||
spacing = max(max(len(k) for k in PRESETS.keys()), 20)
|
spacing = max(max(len(k) for k in PRESETS.keys()), 20)
|
||||||
for name, preset in PRESETS.items():
|
for name, preset in PRESETS.items():
|
||||||
flag = preset.color_text(' ' * spacing, foreground=False)
|
flag = preset.color_text(' ' * spacing, foreground=False)
|
||||||
flags.append([name.center(spacing), flag, flag, flag])
|
flag_entries.append((name, [name.center(spacing), flag, flag, flag], name.lower()))
|
||||||
|
|
||||||
# Calculate flags per row
|
# Calculate flags per row
|
||||||
flags_per_row = term_size()[0] // (spacing + 2)
|
flags_per_row = max(1, term_size()[0] // (spacing + 2))
|
||||||
row_per_page = max(1, (term_size()[1] - 13) // 5)
|
row_per_page = max(1, (term_size()[1] - 13) // 5)
|
||||||
num_pages = ceil(len(flags) / (flags_per_row * row_per_page))
|
flags_per_page = flags_per_row * row_per_page
|
||||||
|
|
||||||
# Create pages
|
def filter_flag_indices(query: str) -> list[int]:
|
||||||
pages = []
|
if not query:
|
||||||
for i in range(num_pages):
|
return list(range(len(flag_entries)))
|
||||||
page = []
|
|
||||||
for j in range(row_per_page):
|
matched = []
|
||||||
page.append(flags[:flags_per_row])
|
for idx, (_, _, lower_name) in enumerate(flag_entries):
|
||||||
flags = flags[flags_per_row:]
|
pos = lower_name.find(query)
|
||||||
if not flags:
|
if pos == -1:
|
||||||
break
|
continue
|
||||||
pages.append(page)
|
matched.append((0 if lower_name.startswith(query) else 1, pos, idx))
|
||||||
|
matched.sort()
|
||||||
|
return [idx for _, _, idx in matched]
|
||||||
|
|
||||||
|
def select_preset_prompt_toolkit() -> str:
|
||||||
|
from prompt_toolkit import PromptSession
|
||||||
|
from prompt_toolkit.application.current import get_app
|
||||||
|
from prompt_toolkit.formatted_text import ANSI
|
||||||
|
from prompt_toolkit.key_binding import KeyBindings
|
||||||
|
|
||||||
|
page_num = 0
|
||||||
|
prompt_header = PRESETS['rainbow'].set_light_dl_def(light_dark).color_text('preset')
|
||||||
|
kb = KeyBindings()
|
||||||
|
last_filter = ''
|
||||||
|
|
||||||
|
def current_num_pages() -> int:
|
||||||
|
text = get_app().current_buffer.text.lower().strip()
|
||||||
|
indices = filter_flag_indices(text)
|
||||||
|
return max(1, ceil(len(indices) / flags_per_page))
|
||||||
|
|
||||||
|
@kb.add('up')
|
||||||
|
def _prev_page(event):
|
||||||
|
nonlocal page_num
|
||||||
|
page_num = (page_num + current_num_pages() - 1) % current_num_pages()
|
||||||
|
event.app.invalidate()
|
||||||
|
|
||||||
|
@kb.add('down')
|
||||||
|
def _next_page(event):
|
||||||
|
nonlocal page_num
|
||||||
|
page_num = (page_num + 1) % current_num_pages()
|
||||||
|
event.app.invalidate()
|
||||||
|
|
||||||
|
@kb.add('escape')
|
||||||
|
def _clear_filter(event):
|
||||||
|
nonlocal page_num
|
||||||
|
event.current_buffer.text = ''
|
||||||
|
event.current_buffer.cursor_position = 0
|
||||||
|
page_num = 0
|
||||||
|
event.app.invalidate()
|
||||||
|
|
||||||
|
def build_preset_lines(text_filter: str):
|
||||||
|
nonlocal page_num
|
||||||
|
filtered_indices = filter_flag_indices(text_filter.lower().strip())
|
||||||
|
num_pages = max(1, ceil(len(filtered_indices) / flags_per_page))
|
||||||
|
page_num = min(page_num, num_pages - 1)
|
||||||
|
|
||||||
|
lines = [
|
||||||
|
color(f"&a{option_counter}. Let's choose a flag!&r"),
|
||||||
|
"Available flag presets:",
|
||||||
|
f"Page: {page_num + 1} of {num_pages}",
|
||||||
|
"",
|
||||||
|
]
|
||||||
|
|
||||||
|
start = page_num * flags_per_page
|
||||||
|
end = min(start + flags_per_page, len(filtered_indices))
|
||||||
|
visible_row_count = 0
|
||||||
|
if start >= end:
|
||||||
|
lines.append("No presets matched this filter.")
|
||||||
|
visible_row_count = 0.2
|
||||||
|
else:
|
||||||
|
current = filtered_indices[start:end]
|
||||||
|
for i in range(0, len(current), flags_per_row):
|
||||||
|
row = [flag_entries[idx][1] for idx in current[i:i + flags_per_row]]
|
||||||
|
lines.extend(' '.join(line) for line in zip(*row))
|
||||||
|
lines.append("")
|
||||||
|
visible_row_count += 1
|
||||||
|
|
||||||
|
# Keep prompt at a fixed vertical position by padding to a full page height.
|
||||||
|
missing_rows = max(0, row_per_page - visible_row_count)
|
||||||
|
lines.extend([""] * round(missing_rows * 5))
|
||||||
|
|
||||||
|
lines.append(
|
||||||
|
"Use the up/down arrow keys to go to the previous/next page. Type to filter and press Enter to select."
|
||||||
|
)
|
||||||
|
lines.append(f"Which {prompt_header} do you want to use? (default: rainbow)")
|
||||||
|
return lines
|
||||||
|
|
||||||
|
def prompt_message():
|
||||||
|
nonlocal last_filter
|
||||||
|
app = get_app()
|
||||||
|
text_filter = app.current_buffer.text
|
||||||
|
last_filter = text_filter
|
||||||
|
|
||||||
|
lines = build_preset_lines(text_filter)
|
||||||
|
return ANSI('\n'.join(lines) + '\n> ')
|
||||||
|
|
||||||
def print_flag_page(page: list[list[list[str]]], page_num: int):
|
|
||||||
clear_screen(title)
|
clear_screen(title)
|
||||||
print_title_prompt("Let's choose a flag!")
|
session = PromptSession(key_bindings=kb)
|
||||||
printc('Available flag presets:')
|
try:
|
||||||
print(f'Page: {page_num + 1} of {num_pages}')
|
selection = session.prompt(prompt_message).strip().lower()
|
||||||
print()
|
except KeyboardInterrupt:
|
||||||
for i in page:
|
clear_screen(title)
|
||||||
print_flag_row(i)
|
for line in build_preset_lines(last_filter):
|
||||||
print()
|
print(line)
|
||||||
|
print(f"> {last_filter}")
|
||||||
|
raise
|
||||||
|
|
||||||
def print_flag_row(current: list[list[str]]):
|
filtered_indices = filter_flag_indices(selection)
|
||||||
[printc(' '.join(line)) for line in zip(*current)]
|
if selection in PRESETS:
|
||||||
print()
|
return selection
|
||||||
|
if filtered_indices:
|
||||||
|
return flag_entries[filtered_indices[0]][0]
|
||||||
|
return 'rainbow'
|
||||||
|
|
||||||
page = 0
|
preset = select_preset_prompt_toolkit()
|
||||||
while True:
|
|
||||||
print_flag_page(pages[page], page)
|
|
||||||
|
|
||||||
tmp = PRESETS['rainbow'].set_light_dl_def(light_dark).color_text('preset')
|
_prs = PRESETS[preset]
|
||||||
opts = list(PRESETS.keys())
|
update_title('Selected flag', _prs.set_light_dl_def(light_dark).color_text(preset))
|
||||||
if page < num_pages - 1:
|
|
||||||
opts.append('next')
|
|
||||||
if page > 0:
|
|
||||||
opts.append('prev')
|
|
||||||
print("Enter 'next' to go to the next page and 'prev' to go to the previous page.")
|
|
||||||
preset = literal_input(f'Which {tmp} do you want to use? ', opts, 'rainbow', show_ops=False)
|
|
||||||
if preset == 'next':
|
|
||||||
page += 1
|
|
||||||
elif preset == 'prev':
|
|
||||||
page -= 1
|
|
||||||
else:
|
|
||||||
_prs = PRESETS[preset]
|
|
||||||
update_title('Selected flag', _prs.set_light_dl_def(light_dark).color_text(preset))
|
|
||||||
break
|
|
||||||
|
|
||||||
#############################
|
#############################
|
||||||
# 4. Dim/lighten colors
|
# 4. Dim/lighten colors
|
||||||
|
|||||||
@@ -26,6 +26,7 @@ classifiers = [
|
|||||||
dependencies = [
|
dependencies = [
|
||||||
# Universal dependencies
|
# Universal dependencies
|
||||||
'typing_extensions; python_version < "3.8"',
|
'typing_extensions; python_version < "3.8"',
|
||||||
|
'prompt_toolkit>=3.0.36',
|
||||||
|
|
||||||
# Windows dependencies
|
# Windows dependencies
|
||||||
'psutil ; platform_system=="Windows"',
|
'psutil ; platform_system=="Windows"',
|
||||||
|
|||||||
Reference in New Issue
Block a user