mirror of
https://github.com/alexpasmantier/television.git
synced 2025-06-03 01:50:12 +00:00
refactor!(cable): update cable channel preview configuration format + add custom preview offsets (#511)
BREAKING CHANGE: the format of the cable channel files and more specifically the preview specification is updated to be a single table named `preview` (with keys `command`, `delimiter`, and `offset`) instead of three flat fields. ```toml preview_command = "echo 3" preview_delimiter = " " preview_offset = "{1}" ``` becomes: ```toml preview.command = "echo 3" preview.delimiter = " " preview.offset = "{1}" ```
This commit is contained in:
parent
39dd9efd5d
commit
ca09c503ca
@ -472,11 +472,11 @@ pub fn draw(c: &mut Criterion) {
|
|||||||
let backend = TestBackend::new(width, height);
|
let backend = TestBackend::new(width, height);
|
||||||
let terminal = Terminal::new(backend).unwrap();
|
let terminal = Terminal::new(backend).unwrap();
|
||||||
let (tx, _) = tokio::sync::mpsc::unbounded_channel();
|
let (tx, _) = tokio::sync::mpsc::unbounded_channel();
|
||||||
let channel = ChannelPrototype::default();
|
let channel_prototype = ChannelPrototype::default();
|
||||||
// Wait for the channel to finish loading
|
// Wait for the channel to finish loading
|
||||||
let mut tv = Television::new(
|
let mut tv = Television::new(
|
||||||
tx,
|
tx,
|
||||||
channel,
|
&channel_prototype,
|
||||||
config,
|
config,
|
||||||
None,
|
None,
|
||||||
false,
|
false,
|
||||||
|
@ -2,26 +2,27 @@
|
|||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "files"
|
name = "files"
|
||||||
source_command = "fd -t f"
|
source_command = "fd -t f"
|
||||||
preview_command = "bat -n --color=always {}"
|
preview.command = "bat -n --color=always {}"
|
||||||
|
|
||||||
# Text
|
# Text
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "text"
|
name = "text"
|
||||||
source_command = "rg . --no-heading --line-number"
|
source_command = "rg . --no-heading --line-number"
|
||||||
preview_command = "bat -n --color=always {0} -H {1}"
|
preview.command = "bat -n --color=always {0}"
|
||||||
preview_delimiter = ":"
|
preview.delimiter = ":"
|
||||||
|
preview.offset = "{1}"
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
source_command = "fd -t d"
|
source_command = "fd -t d"
|
||||||
preview_command = "ls -la --color=always {}"
|
preview.command = "ls -la --color=always {}"
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "env"
|
name = "env"
|
||||||
source_command = "printenv"
|
source_command = "printenv"
|
||||||
preview_command = "cut -d= -f2 <<< ${0} | cut -d\" \" -f2- | sed 's/:/\\n/g'"
|
preview.command = "cut -d= -f2 <<< ${0} | cut -d\" \" -f2- | sed 's/:/\\n/g'"
|
||||||
|
|
||||||
# Aliases
|
# Aliases
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
@ -32,47 +33,50 @@ interactive = true
|
|||||||
# GIT
|
# GIT
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-repos"
|
name = "git-repos"
|
||||||
# this is a MacOS version but feel free to change it to fit your needs
|
# this is a MacOS version but feel free to override it to fit your needs
|
||||||
source_command = "fd -g .git -HL -t d -d 10 --prune ~ -E 'Library' -E 'Application Support' --exec dirname {}"
|
source_command = "fd -g .git -HL -t d -d 10 --prune ~ -E 'Library' -E 'Application Support' --exec dirname {}"
|
||||||
preview_command = "cd {} && git log -n 200 --pretty=medium --all --graph --color"
|
preview.command = "cd {} && git log -n 200 --pretty=medium --all --graph --color"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-diff"
|
name = "git-diff"
|
||||||
source_command = "git diff --name-only"
|
source_command = "git diff --name-only"
|
||||||
preview_command = "git diff --color=always {0}"
|
preview.command = "git diff --color=always {0}"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-reflog"
|
name = "git-reflog"
|
||||||
source_command = 'git reflog'
|
source_command = 'git reflog'
|
||||||
preview_command = 'git show -p --stat --pretty=fuller --color=always {0}'
|
preview.command = 'git show -p --stat --pretty=fuller --color=always {0}'
|
||||||
|
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-log"
|
name = "git-log"
|
||||||
source_command = "git log --oneline --date=short --pretty=\"format:%h %s %an %cd\" \"$@\""
|
source_command = "git log --oneline --date=short --pretty=\"format:%h %s %an %cd\" \"$@\""
|
||||||
preview_command = "git show -p --stat --pretty=fuller --color=always {0}"
|
preview.command = 'git show -p --stat --pretty=fuller --color=always {0}'
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-branch"
|
name = "git-branch"
|
||||||
source_command = "git --no-pager branch --all --format=\"%(refname:short)\""
|
source_command = "git --no-pager branch --all --format=\"%(refname:short)\""
|
||||||
preview_command = "git show -p --stat --pretty=fuller --color=always {0}"
|
preview.command = 'git show -p --stat --pretty=fuller --color=always {0}'
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "docker-images"
|
name = "docker-images"
|
||||||
source_command = "docker image list --format \"{{.ID}}\""
|
source_command = "docker image list --format \"{{.ID}}\""
|
||||||
preview_command = "docker image inspect {0} | jq -C"
|
preview.command = "docker image inspect {0} | jq -C"
|
||||||
|
|
||||||
|
|
||||||
# S3
|
# S3
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "s3-buckets"
|
name = "s3-buckets"
|
||||||
source_command = "aws s3 ls | cut -d \" \" -f 3"
|
source_command = "aws s3 ls | cut -d \" \" -f 3"
|
||||||
preview_command = "aws s3 ls s3://{0}"
|
preview.command = "aws s3 ls s3://{0}"
|
||||||
|
|
||||||
|
|
||||||
# Dotfiles
|
# Dotfiles
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "my-dotfiles"
|
name = "my-dotfiles"
|
||||||
source_command = "fd -t f . $HOME/.config"
|
source_command = "fd -t f . $HOME/.config"
|
||||||
preview_command = "bat -n --color=always {}"
|
preview.command = "bat -n --color=always {}"
|
||||||
|
|
||||||
# Shell history
|
# Shell history
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
|
@ -2,13 +2,13 @@
|
|||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "files"
|
name = "files"
|
||||||
source_command = "Get-ChildItem -Recurse -File | Select-Object -ExpandProperty FullName"
|
source_command = "Get-ChildItem -Recurse -File | Select-Object -ExpandProperty FullName"
|
||||||
preview_command = "bat -n --color=always {}"
|
preview.command = "bat -n --color=always {}"
|
||||||
|
|
||||||
# Directories
|
# Directories
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "dirs"
|
name = "dirs"
|
||||||
source_command = "Get-ChildItem -Recurse -Directory | Select-Object -ExpandProperty FullName"
|
source_command = "Get-ChildItem -Recurse -Directory | Select-Object -ExpandProperty FullName"
|
||||||
preview_command = "ls -l {}"
|
preview.command = "ls -l {}"
|
||||||
|
|
||||||
# Environment variables
|
# Environment variables
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
@ -24,39 +24,39 @@ source_command = "Get-Alias"
|
|||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-repos"
|
name = "git-repos"
|
||||||
source_command = "Get-ChildItem -Path 'C:\\Users' -Recurse -Directory -Force -ErrorAction SilentlyContinue | Where-Object { Test-Path \"$($_.FullName)\\.git\" } | Select-Object -ExpandProperty FullName"
|
source_command = "Get-ChildItem -Path 'C:\\Users' -Recurse -Directory -Force -ErrorAction SilentlyContinue | Where-Object { Test-Path \"$($_.FullName)\\.git\" } | Select-Object -ExpandProperty FullName"
|
||||||
preview_command = "cd '{}' ; git log -n 200 --pretty=medium --all --graph --color"
|
preview.command = "cd '{}' ; git log -n 200 --pretty=medium --all --graph --color"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-diff"
|
name = "git-diff"
|
||||||
source_command = "git diff --name-only"
|
source_command = "git diff --name-only"
|
||||||
preview_command = "git diff --color=always {}"
|
preview.command = "git diff --color=always {}"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-reflog"
|
name = "git-reflog"
|
||||||
source_command = "git reflog"
|
source_command = "git reflog"
|
||||||
preview_command = "git show -p --stat --pretty=fuller --color=always {0}"
|
preview.command = "git show -p --stat --pretty=fuller --color=always {0}"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-log"
|
name = "git-log"
|
||||||
source_command = "git log --oneline --date=short --pretty='format:%h %s %an %cd'"
|
source_command = "git log --oneline --date=short --pretty='format:%h %s %an %cd'"
|
||||||
preview_command = "git show -p --stat --pretty=fuller --color=always {0}"
|
preview.command = "git show -p --stat --pretty=fuller --color=always {0}"
|
||||||
|
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "git-branch"
|
name = "git-branch"
|
||||||
source_command = "git branch --all --format='%(refname:short)'"
|
source_command = "git branch --all --format='%(refname:short)'"
|
||||||
preview_command = "git show -p --stat --pretty=fuller --color=always {0}"
|
preview.command = "git show -p --stat --pretty=fuller --color=always {0}"
|
||||||
|
|
||||||
# Docker
|
# Docker
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "docker-images"
|
name = "docker-images"
|
||||||
source_command = "docker image ls --format '{{.ID}}'"
|
source_command = "docker image ls --format '{{.ID}}'"
|
||||||
preview_command = "docker image inspect {0} | jq -C"
|
preview.command = "docker image inspect {0} | jq -C"
|
||||||
|
|
||||||
# Dotfiles (adapted to common Windows dotfile locations)
|
# Dotfiles (adapted to common Windows dotfile locations)
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
name = "my-dotfiles"
|
name = "my-dotfiles"
|
||||||
source_command = "Get-ChildItem -Recurse -File -Path \"$env:USERPROFILE\\AppData\\Roaming\\\""
|
source_command = "Get-ChildItem -Recurse -File -Path \"$env:USERPROFILE\\AppData\\Roaming\\\""
|
||||||
preview_command = "bat -n --color=always {}"
|
preview.command = "bat -n --color=always {}"
|
||||||
|
|
||||||
# Shell history
|
# Shell history
|
||||||
[[cable_channel]]
|
[[cable_channel]]
|
||||||
|
@ -137,7 +137,7 @@ const ACTION_BUF_SIZE: usize = 8;
|
|||||||
|
|
||||||
impl App {
|
impl App {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
channel_prototype: ChannelPrototype,
|
channel_prototype: &ChannelPrototype,
|
||||||
config: Config,
|
config: Config,
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
options: AppOptions,
|
options: AppOptions,
|
||||||
|
@ -80,11 +80,10 @@ pub fn load_cable() -> Result<Cable> {
|
|||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
debug!(
|
debug!("Loaded {} custom cable channels", prototypes.len());
|
||||||
"Loaded {} default and {} custom prototypes",
|
if prototypes.is_empty() {
|
||||||
default_prototypes.prototypes.len(),
|
debug!("No custom cable channels found");
|
||||||
prototypes.len()
|
}
|
||||||
);
|
|
||||||
|
|
||||||
let mut cable_channels = FxHashMap::default();
|
let mut cable_channels = FxHashMap::default();
|
||||||
// custom prototypes take precedence over default ones
|
// custom prototypes take precedence over default ones
|
||||||
|
@ -6,14 +6,14 @@ use rustc_hash::{FxBuildHasher, FxHashSet};
|
|||||||
use tracing::debug;
|
use tracing::debug;
|
||||||
|
|
||||||
use crate::channels::{
|
use crate::channels::{
|
||||||
entry::Entry,
|
entry::Entry, preview::PreviewCommand, prototypes::ChannelPrototype,
|
||||||
preview::PreviewCommand,
|
|
||||||
prototypes::{ChannelPrototype, DEFAULT_DELIMITER},
|
|
||||||
};
|
};
|
||||||
use crate::matcher::Matcher;
|
use crate::matcher::Matcher;
|
||||||
use crate::matcher::{config::Config, injector::Injector};
|
use crate::matcher::{config::Config, injector::Injector};
|
||||||
use crate::utils::command::shell_command;
|
use crate::utils::command::shell_command;
|
||||||
|
|
||||||
|
use super::prototypes::format_prototype_string;
|
||||||
|
|
||||||
pub struct Channel {
|
pub struct Channel {
|
||||||
pub name: String,
|
pub name: String,
|
||||||
matcher: Matcher<String>,
|
matcher: Matcher<String>,
|
||||||
@ -24,53 +24,28 @@ pub struct Channel {
|
|||||||
|
|
||||||
impl Default for Channel {
|
impl Default for Channel {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
Self::new(
|
Self::new(&ChannelPrototype::new(
|
||||||
"files",
|
"files",
|
||||||
"find . -type f",
|
"find . -type f",
|
||||||
false,
|
false,
|
||||||
Some(PreviewCommand::new("cat {}", ":", None)),
|
Some(PreviewCommand::new("cat {}", ":", None)),
|
||||||
)
|
))
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<ChannelPrototype> for Channel {
|
|
||||||
fn from(prototype: ChannelPrototype) -> Self {
|
|
||||||
Self::new(
|
|
||||||
&prototype.name,
|
|
||||||
&prototype.source_command,
|
|
||||||
prototype.interactive,
|
|
||||||
match prototype.preview_command {
|
|
||||||
Some(command) => Some(PreviewCommand::new(
|
|
||||||
&command,
|
|
||||||
&prototype
|
|
||||||
.preview_delimiter
|
|
||||||
.unwrap_or(DEFAULT_DELIMITER.to_string()),
|
|
||||||
prototype.preview_offset,
|
|
||||||
)),
|
|
||||||
None => None,
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Channel {
|
impl Channel {
|
||||||
pub fn new(
|
pub fn new(prototype: &ChannelPrototype) -> Self {
|
||||||
name: &str,
|
|
||||||
entries_command: &str,
|
|
||||||
interactive: bool,
|
|
||||||
preview_command: Option<PreviewCommand>,
|
|
||||||
) -> Self {
|
|
||||||
let matcher = Matcher::new(Config::default());
|
let matcher = Matcher::new(Config::default());
|
||||||
let injector = matcher.injector();
|
let injector = matcher.injector();
|
||||||
let crawl_handle = tokio::spawn(load_candidates(
|
let crawl_handle = tokio::spawn(load_candidates(
|
||||||
entries_command.to_string(),
|
prototype.source_command.to_string(),
|
||||||
interactive,
|
prototype.interactive,
|
||||||
injector,
|
injector,
|
||||||
));
|
));
|
||||||
Self {
|
Self {
|
||||||
matcher,
|
matcher,
|
||||||
preview_command,
|
preview_command: prototype.preview_command.clone(),
|
||||||
name: name.to_string(),
|
name: prototype.name.to_string(),
|
||||||
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
selected_entries: HashSet::with_hasher(FxBuildHasher),
|
||||||
crawl_handle,
|
crawl_handle,
|
||||||
}
|
}
|
||||||
@ -94,8 +69,27 @@ impl Channel {
|
|||||||
|
|
||||||
pub fn get_result(&self, index: u32) -> Option<Entry> {
|
pub fn get_result(&self, index: u32) -> Option<Entry> {
|
||||||
self.matcher.get_result(index).map(|item| {
|
self.matcher.get_result(index).map(|item| {
|
||||||
let path = item.matched_string;
|
let name = item.matched_string;
|
||||||
Entry::new(path)
|
if let Some(cmd) = &self.preview_command {
|
||||||
|
if let Some(offset_expr) = &cmd.offset_expr {
|
||||||
|
let offset_string = format_prototype_string(
|
||||||
|
offset_expr,
|
||||||
|
&name,
|
||||||
|
&cmd.delimiter,
|
||||||
|
);
|
||||||
|
let offset_str = {
|
||||||
|
offset_string
|
||||||
|
.strip_prefix('\'')
|
||||||
|
.and_then(|s| s.strip_suffix('\''))
|
||||||
|
.unwrap_or(&offset_string)
|
||||||
|
};
|
||||||
|
|
||||||
|
return Entry::new(name).with_line_number(
|
||||||
|
offset_str.parse::<usize>().unwrap(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Entry::new(name)
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,21 +1,27 @@
|
|||||||
use std::fmt::Display;
|
use std::fmt::Display;
|
||||||
|
|
||||||
use crate::channels::{
|
use serde::Deserialize;
|
||||||
entry::Entry,
|
|
||||||
prototypes::{ChannelPrototype, DEFAULT_DELIMITER},
|
|
||||||
};
|
|
||||||
use lazy_regex::{regex, Lazy, Regex};
|
|
||||||
use tracing::debug;
|
|
||||||
|
|
||||||
static CMD_RE: &Lazy<Regex> = regex!(r"\{(\d+)\}");
|
use crate::channels::{entry::Entry, prototypes::format_prototype_string};
|
||||||
|
|
||||||
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default)]
|
#[derive(Debug, Clone, Eq, PartialEq, Hash, Default, Deserialize)]
|
||||||
pub struct PreviewCommand {
|
pub struct PreviewCommand {
|
||||||
pub command: String,
|
pub command: String,
|
||||||
|
#[serde(default = "default_delimiter")]
|
||||||
pub delimiter: String,
|
pub delimiter: String,
|
||||||
|
#[serde(rename = "offset")]
|
||||||
pub offset_expr: Option<String>,
|
pub offset_expr: Option<String>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub const DEFAULT_DELIMITER: &str = " ";
|
||||||
|
|
||||||
|
/// The default delimiter to use for the preview command to use to split
|
||||||
|
/// entries into multiple referenceable parts.
|
||||||
|
#[allow(clippy::unnecessary_wraps)]
|
||||||
|
fn default_delimiter() -> String {
|
||||||
|
DEFAULT_DELIMITER.to_string()
|
||||||
|
}
|
||||||
|
|
||||||
impl PreviewCommand {
|
impl PreviewCommand {
|
||||||
pub fn new(
|
pub fn new(
|
||||||
command: &str,
|
command: &str,
|
||||||
@ -47,43 +53,7 @@ impl PreviewCommand {
|
|||||||
/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'");
|
/// assert_eq!(formatted_command, "something 'a:given:entry:to:preview' 'entry' 'a'");
|
||||||
/// ```
|
/// ```
|
||||||
pub fn format_with(&self, entry: &Entry) -> String {
|
pub fn format_with(&self, entry: &Entry) -> String {
|
||||||
let parts = entry.name.split(&self.delimiter).collect::<Vec<&str>>();
|
format_prototype_string(&self.command, &entry.name, &self.delimiter)
|
||||||
|
|
||||||
let mut formatted_command = self
|
|
||||||
.command
|
|
||||||
.replace("{}", format!("'{}'", entry.name).as_str());
|
|
||||||
debug!("FORMATTED_COMMAND: {formatted_command}");
|
|
||||||
debug!("PARTS: {parts:?}");
|
|
||||||
|
|
||||||
formatted_command = CMD_RE
|
|
||||||
.replace_all(&formatted_command, |caps: ®ex::Captures| {
|
|
||||||
let index =
|
|
||||||
caps.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
|
||||||
format!("'{}'", parts[index])
|
|
||||||
})
|
|
||||||
.to_string();
|
|
||||||
|
|
||||||
formatted_command
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl From<&ChannelPrototype> for Option<PreviewCommand> {
|
|
||||||
fn from(value: &ChannelPrototype) -> Self {
|
|
||||||
if let Some(command) = value.preview_command.as_ref() {
|
|
||||||
let delimiter = value
|
|
||||||
.preview_delimiter
|
|
||||||
.as_ref()
|
|
||||||
.map_or(DEFAULT_DELIMITER, |v| v);
|
|
||||||
|
|
||||||
let offset_expr = value.preview_offset.clone();
|
|
||||||
|
|
||||||
// FIXME: handle offset here (side note: we don't want to reparse the offset
|
|
||||||
// expression for each entry, so maybe just parse it once and try to store it
|
|
||||||
// as some sort of function we can call later on
|
|
||||||
Some(PreviewCommand::new(command, delimiter, offset_expr))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
use lazy_regex::{regex, Lazy, Regex};
|
||||||
use rustc_hash::FxHashMap;
|
use rustc_hash::FxHashMap;
|
||||||
use std::{
|
use std::{
|
||||||
fmt::{self, Display, Formatter},
|
fmt::{self, Display, Formatter},
|
||||||
@ -43,10 +44,8 @@ pub struct ChannelPrototype {
|
|||||||
pub source_command: String,
|
pub source_command: String,
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub interactive: bool,
|
pub interactive: bool,
|
||||||
pub preview_command: Option<String>,
|
#[serde(rename = "preview")]
|
||||||
#[serde(default = "default_delimiter")]
|
pub preview_command: Option<PreviewCommand>,
|
||||||
pub preview_delimiter: Option<String>,
|
|
||||||
pub preview_offset: Option<String>,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const STDIN_CHANNEL_NAME: &str = "stdin";
|
const STDIN_CHANNEL_NAME: &str = "stdin";
|
||||||
@ -57,52 +56,27 @@ impl ChannelPrototype {
|
|||||||
name: &str,
|
name: &str,
|
||||||
source_command: &str,
|
source_command: &str,
|
||||||
interactive: bool,
|
interactive: bool,
|
||||||
preview_command: Option<String>,
|
preview_command: Option<PreviewCommand>,
|
||||||
preview_delimiter: Option<String>,
|
|
||||||
preview_offset: Option<String>,
|
|
||||||
) -> Self {
|
) -> Self {
|
||||||
Self {
|
Self {
|
||||||
name: name.to_string(),
|
name: name.to_string(),
|
||||||
source_command: source_command.to_string(),
|
source_command: source_command.to_string(),
|
||||||
interactive,
|
interactive,
|
||||||
preview_command,
|
preview_command,
|
||||||
preview_delimiter,
|
|
||||||
preview_offset,
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn stdin(preview: Option<PreviewCommand>) -> Self {
|
pub fn stdin(preview: Option<PreviewCommand>) -> Self {
|
||||||
match preview {
|
Self {
|
||||||
Some(PreviewCommand {
|
name: STDIN_CHANNEL_NAME.to_string(),
|
||||||
command,
|
source_command: STDIN_SOURCE_COMMAND.to_string(),
|
||||||
delimiter,
|
interactive: false,
|
||||||
offset_expr,
|
preview_command: preview,
|
||||||
}) => Self {
|
|
||||||
name: STDIN_CHANNEL_NAME.to_string(),
|
|
||||||
source_command: STDIN_SOURCE_COMMAND.to_string(),
|
|
||||||
interactive: false,
|
|
||||||
preview_command: Some(command),
|
|
||||||
preview_delimiter: Some(delimiter),
|
|
||||||
preview_offset: offset_expr,
|
|
||||||
},
|
|
||||||
None => Self {
|
|
||||||
name: STDIN_CHANNEL_NAME.to_string(),
|
|
||||||
source_command: STDIN_SOURCE_COMMAND.to_string(),
|
|
||||||
interactive: false,
|
|
||||||
preview_command: None,
|
|
||||||
preview_delimiter: None,
|
|
||||||
preview_offset: None,
|
|
||||||
},
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn preview_command(&self) -> Option<PreviewCommand> {
|
|
||||||
self.into()
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const DEFAULT_PROTOTYPE_NAME: &str = "files";
|
pub const DEFAULT_PROTOTYPE_NAME: &str = "files";
|
||||||
pub const DEFAULT_DELIMITER: &str = " ";
|
|
||||||
|
|
||||||
impl Default for ChannelPrototype {
|
impl Default for ChannelPrototype {
|
||||||
fn default() -> Self {
|
fn default() -> Self {
|
||||||
@ -113,19 +87,35 @@ impl Default for ChannelPrototype {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/// The default delimiter to use for the preview command to use to split
|
|
||||||
/// entries into multiple referenceable parts.
|
|
||||||
#[allow(clippy::unnecessary_wraps)]
|
|
||||||
fn default_delimiter() -> Option<String> {
|
|
||||||
Some(DEFAULT_DELIMITER.to_string())
|
|
||||||
}
|
|
||||||
|
|
||||||
impl Display for ChannelPrototype {
|
impl Display for ChannelPrototype {
|
||||||
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
fn fmt(&self, f: &mut Formatter) -> fmt::Result {
|
||||||
write!(f, "{}", self.name)
|
write!(f, "{}", self.name)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub static CMD_RE: &Lazy<Regex> = regex!(r"\{(\d+)\}");
|
||||||
|
|
||||||
|
pub fn format_prototype_string(
|
||||||
|
template: &str,
|
||||||
|
source: &str,
|
||||||
|
delimiter: &str,
|
||||||
|
) -> String {
|
||||||
|
let parts = source.split(delimiter).collect::<Vec<&str>>();
|
||||||
|
|
||||||
|
let mut formatted_string =
|
||||||
|
template.replace("{}", format!("'{}'", source).as_str());
|
||||||
|
|
||||||
|
formatted_string = CMD_RE
|
||||||
|
.replace_all(&formatted_string, |caps: ®ex::Captures| {
|
||||||
|
let index =
|
||||||
|
caps.get(1).unwrap().as_str().parse::<usize>().unwrap();
|
||||||
|
format!("'{}'", parts[index])
|
||||||
|
})
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
formatted_string
|
||||||
|
}
|
||||||
|
|
||||||
/// A neat `HashMap` of channel prototypes indexed by their name.
|
/// A neat `HashMap` of channel prototypes indexed by their name.
|
||||||
///
|
///
|
||||||
/// This is used to store cable channel prototypes throughout the application
|
/// This is used to store cable channel prototypes throughout the application
|
||||||
@ -142,6 +132,16 @@ impl Deref for Cable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl Cable {
|
||||||
|
pub fn default_channel(&self) -> ChannelPrototype {
|
||||||
|
self.get(DEFAULT_PROTOTYPE_NAME)
|
||||||
|
.cloned()
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
panic!("Default channel '{DEFAULT_PROTOTYPE_NAME}' not found")
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// A default cable channels specification that is compiled into the
|
/// A default cable channels specification that is compiled into the
|
||||||
/// application.
|
/// application.
|
||||||
#[cfg(unix)]
|
#[cfg(unix)]
|
||||||
|
@ -25,6 +25,14 @@ pub struct Cli {
|
|||||||
#[arg(short, long, value_name = "STRING", verbatim_doc_comment)]
|
#[arg(short, long, value_name = "STRING", verbatim_doc_comment)]
|
||||||
pub preview: Option<String>,
|
pub preview: Option<String>,
|
||||||
|
|
||||||
|
/// A preview line number offset template to use to scroll the preview to for each
|
||||||
|
/// entry.
|
||||||
|
///
|
||||||
|
/// This template uses the same syntax as the `preview` option and will be formatted
|
||||||
|
/// using the currently selected entry.
|
||||||
|
#[arg(long, value_name = "STRING", verbatim_doc_comment)]
|
||||||
|
pub preview_offset: Option<String>,
|
||||||
|
|
||||||
/// Disable the preview panel entirely on startup.
|
/// Disable the preview panel entirely on startup.
|
||||||
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
#[arg(long, default_value = "false", verbatim_doc_comment)]
|
||||||
pub no_preview: bool,
|
pub no_preview: bool,
|
||||||
|
@ -11,7 +11,7 @@ use crate::{
|
|||||||
prototypes::{Cable, ChannelPrototype},
|
prototypes::{Cable, ChannelPrototype},
|
||||||
},
|
},
|
||||||
cli::args::{Cli, Command},
|
cli::args::{Cli, Command},
|
||||||
config::{get_config_dir, get_data_dir, KeyBindings, DEFAULT_CHANNEL},
|
config::{get_config_dir, get_data_dir, KeyBindings},
|
||||||
};
|
};
|
||||||
|
|
||||||
pub mod args;
|
pub mod args;
|
||||||
@ -75,23 +75,19 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
let preview_command = cli.preview.map(|preview| PreviewCommand {
|
let preview_command = cli.preview.map(|preview| PreviewCommand {
|
||||||
command: preview,
|
command: preview,
|
||||||
delimiter: cli.delimiter.clone(),
|
delimiter: cli.delimiter.clone(),
|
||||||
// TODO: add the --preview-offset option to the CLI
|
offset_expr: cli.preview_offset.clone(),
|
||||||
offset_expr: None,
|
|
||||||
});
|
});
|
||||||
|
|
||||||
let mut channel: ChannelPrototype;
|
let mut channel: ChannelPrototype;
|
||||||
let working_directory: Option<String>;
|
let working_directory: Option<String>;
|
||||||
|
|
||||||
let cable_channels = cable::load_cable().unwrap_or_default();
|
let cable = cable::load_cable().unwrap_or_default();
|
||||||
if cli.channel.is_none() {
|
if cli.channel.is_none() {
|
||||||
channel = cable_channels
|
channel = cable.default_channel();
|
||||||
.get(DEFAULT_CHANNEL)
|
|
||||||
.expect("Default channel not found in cable channels")
|
|
||||||
.clone();
|
|
||||||
working_directory = cli.working_directory;
|
working_directory = cli.working_directory;
|
||||||
} else {
|
} else {
|
||||||
let cli_channel = cli.channel.as_ref().unwrap().to_owned();
|
let cli_channel = cli.channel.as_ref().unwrap().to_owned();
|
||||||
match parse_channel(&cli_channel, &cable_channels) {
|
match parse_channel(&cli_channel, &cable) {
|
||||||
Ok(p) => {
|
Ok(p) => {
|
||||||
channel = p;
|
channel = p;
|
||||||
working_directory = cli.working_directory;
|
working_directory = cli.working_directory;
|
||||||
@ -102,12 +98,7 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
if cli.working_directory.is_none()
|
if cli.working_directory.is_none()
|
||||||
&& Path::new(&cli_channel).exists()
|
&& Path::new(&cli_channel).exists()
|
||||||
{
|
{
|
||||||
channel = cable_channels
|
channel = cable.default_channel();
|
||||||
.get(DEFAULT_CHANNEL)
|
|
||||||
.expect(
|
|
||||||
"Default channel not found in cable channels",
|
|
||||||
)
|
|
||||||
.clone();
|
|
||||||
working_directory = Some(cli.channel.unwrap().clone());
|
working_directory = Some(cli.channel.unwrap().clone());
|
||||||
} else {
|
} else {
|
||||||
unknown_channel_exit(&cli.channel.unwrap());
|
unknown_channel_exit(&cli.channel.unwrap());
|
||||||
@ -117,10 +108,9 @@ impl From<Cli> for PostProcessedCli {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// override the default previewer
|
||||||
if let Some(preview_cmd) = &preview_command {
|
if let Some(preview_cmd) = &preview_command {
|
||||||
channel.preview_command = Some(preview_cmd.command.clone());
|
channel.preview_command = Some(preview_cmd.clone());
|
||||||
channel.preview_delimiter = Some(preview_cmd.delimiter.clone());
|
|
||||||
channel.preview_offset.clone_from(&preview_cmd.offset_expr);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Self {
|
Self {
|
||||||
@ -317,7 +307,11 @@ mod tests {
|
|||||||
let post_processed_cli: PostProcessedCli = cli.into();
|
let post_processed_cli: PostProcessedCli = cli.into();
|
||||||
|
|
||||||
let expected = ChannelPrototype {
|
let expected = ChannelPrototype {
|
||||||
preview_delimiter: Some(":".to_string()),
|
preview_command: Some(PreviewCommand {
|
||||||
|
command: "bat -n --color=always {}".to_string(),
|
||||||
|
delimiter: ":".to_string(),
|
||||||
|
offset_expr: None,
|
||||||
|
}),
|
||||||
..Default::default()
|
..Default::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@ pub use themes::Theme;
|
|||||||
use tracing::{debug, warn};
|
use tracing::{debug, warn};
|
||||||
pub use ui::UiConfig;
|
pub use ui::UiConfig;
|
||||||
|
|
||||||
|
use crate::channels::prototypes::DEFAULT_PROTOTYPE_NAME;
|
||||||
|
|
||||||
mod keybindings;
|
mod keybindings;
|
||||||
pub mod shell_integration;
|
pub mod shell_integration;
|
||||||
mod themes;
|
mod themes;
|
||||||
@ -39,10 +41,8 @@ pub struct AppConfig {
|
|||||||
pub default_channel: String,
|
pub default_channel: String,
|
||||||
}
|
}
|
||||||
|
|
||||||
pub const DEFAULT_CHANNEL: &str = "files";
|
|
||||||
|
|
||||||
fn default_channel() -> String {
|
fn default_channel() -> String {
|
||||||
DEFAULT_CHANNEL.to_string()
|
DEFAULT_PROTOTYPE_NAME.to_string()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Hash for AppConfig {
|
impl Hash for AppConfig {
|
||||||
|
@ -60,7 +60,7 @@ async fn main() -> Result<()> {
|
|||||||
|
|
||||||
// determine the channel to use based on the CLI arguments and configuration
|
// determine the channel to use based on the CLI arguments and configuration
|
||||||
debug!("Determining channel...");
|
debug!("Determining channel...");
|
||||||
let channel = determine_channel(
|
let channel_prototype = determine_channel(
|
||||||
args.clone(),
|
args.clone(),
|
||||||
&config,
|
&config,
|
||||||
is_readable_stdin(),
|
is_readable_stdin(),
|
||||||
@ -77,8 +77,13 @@ async fn main() -> Result<()> {
|
|||||||
args.no_help,
|
args.no_help,
|
||||||
config.application.tick_rate,
|
config.application.tick_rate,
|
||||||
);
|
);
|
||||||
let mut app =
|
let mut app = App::new(
|
||||||
App::new(channel, config, args.input, options, &cable_channels);
|
&channel_prototype,
|
||||||
|
config,
|
||||||
|
args.input,
|
||||||
|
options,
|
||||||
|
&cable_channels,
|
||||||
|
);
|
||||||
stdout().flush()?;
|
stdout().flush()?;
|
||||||
debug!("Running application...");
|
debug!("Running application...");
|
||||||
let output = app.run(stdout().is_terminal(), false).await?;
|
let output = app.run(stdout().is_terminal(), false).await?;
|
||||||
@ -217,7 +222,7 @@ mod tests {
|
|||||||
&args,
|
&args,
|
||||||
&config,
|
&config,
|
||||||
true,
|
true,
|
||||||
&ChannelPrototype::new("stdin", "cat", false, None, None, None),
|
&ChannelPrototype::new("stdin", "cat", false, None),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -226,7 +231,7 @@ mod tests {
|
|||||||
async fn test_determine_channel_autocomplete_prompt() {
|
async fn test_determine_channel_autocomplete_prompt() {
|
||||||
let autocomplete_prompt = Some("cd".to_string());
|
let autocomplete_prompt = Some("cd".to_string());
|
||||||
let expected_channel =
|
let expected_channel =
|
||||||
ChannelPrototype::new("dirs", "ls {}", false, None, None, None);
|
ChannelPrototype::new("dirs", "ls {}", false, None);
|
||||||
let args = PostProcessedCli {
|
let args = PostProcessedCli {
|
||||||
autocomplete_prompt,
|
autocomplete_prompt,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -258,8 +263,7 @@ mod tests {
|
|||||||
|
|
||||||
#[tokio::test]
|
#[tokio::test]
|
||||||
async fn test_determine_channel_standard_case() {
|
async fn test_determine_channel_standard_case() {
|
||||||
let channel =
|
let channel = ChannelPrototype::new("dirs", "", false, None);
|
||||||
ChannelPrototype::new("dirs", "", false, None, None, None);
|
|
||||||
let args = PostProcessedCli {
|
let args = PostProcessedCli {
|
||||||
channel,
|
channel,
|
||||||
..Default::default()
|
..Default::default()
|
||||||
@ -269,7 +273,7 @@ mod tests {
|
|||||||
&args,
|
&args,
|
||||||
&config,
|
&config,
|
||||||
false,
|
false,
|
||||||
&ChannelPrototype::new("dirs", "", false, None, None, None),
|
&ChannelPrototype::new("dirs", "", false, None),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
@ -9,7 +9,7 @@ pub struct PreviewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const PREVIEW_MIN_SCROLL_LINES: u16 = 3;
|
const PREVIEW_MIN_SCROLL_LINES: u16 = 3;
|
||||||
pub const ANSI_BEFORE_CONTEXT_SIZE: u16 = 10;
|
pub const ANSI_BEFORE_CONTEXT_SIZE: u16 = 3;
|
||||||
const ANSI_CONTEXT_SIZE: usize = 500;
|
const ANSI_CONTEXT_SIZE: usize = 500;
|
||||||
|
|
||||||
impl PreviewState {
|
impl PreviewState {
|
||||||
@ -51,7 +51,7 @@ impl PreviewState {
|
|||||||
scroll: u16,
|
scroll: u16,
|
||||||
target_line: Option<u16>,
|
target_line: Option<u16>,
|
||||||
) {
|
) {
|
||||||
if self.preview.title != preview.title {
|
if self.preview.title != preview.title || self.scroll != scroll {
|
||||||
self.preview = preview;
|
self.preview = preview;
|
||||||
self.scroll = scroll;
|
self.scroll = scroll;
|
||||||
self.target_line = target_line;
|
self.target_line = target_line;
|
||||||
@ -59,16 +59,31 @@ impl PreviewState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn for_render_context(&self) -> Self {
|
pub fn for_render_context(&self) -> Self {
|
||||||
let skipped_lines =
|
let num_skipped_lines =
|
||||||
self.scroll.saturating_sub(ANSI_BEFORE_CONTEXT_SIZE);
|
self.scroll.saturating_sub(ANSI_BEFORE_CONTEXT_SIZE);
|
||||||
let cropped_content = self
|
let cropped_content = self
|
||||||
.preview
|
.preview
|
||||||
.content
|
.content
|
||||||
.lines()
|
.lines()
|
||||||
.skip(skipped_lines as usize)
|
.skip(num_skipped_lines as usize)
|
||||||
.take(ANSI_CONTEXT_SIZE)
|
.take(ANSI_CONTEXT_SIZE)
|
||||||
.collect::<Vec<_>>()
|
.collect::<Vec<_>>()
|
||||||
.join("\n");
|
.join("\n");
|
||||||
|
|
||||||
|
let target_line: Option<u16> =
|
||||||
|
if let Some(target_line) = self.target_line {
|
||||||
|
if num_skipped_lines < target_line
|
||||||
|
&& (target_line - num_skipped_lines)
|
||||||
|
<= u16::try_from(ANSI_CONTEXT_SIZE).unwrap()
|
||||||
|
{
|
||||||
|
Some(target_line.saturating_sub(num_skipped_lines))
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
None
|
||||||
|
};
|
||||||
|
|
||||||
PreviewState::new(
|
PreviewState::new(
|
||||||
self.enabled,
|
self.enabled,
|
||||||
Preview::new(
|
Preview::new(
|
||||||
@ -77,8 +92,8 @@ impl PreviewState {
|
|||||||
self.preview.icon,
|
self.preview.icon,
|
||||||
self.preview.total_lines,
|
self.preview.total_lines,
|
||||||
),
|
),
|
||||||
skipped_lines,
|
num_skipped_lines,
|
||||||
self.target_line,
|
target_line,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -32,13 +32,19 @@ pub fn draw_preview_content_block(
|
|||||||
use_nerd_font_icons,
|
use_nerd_font_icons,
|
||||||
)?;
|
)?;
|
||||||
// render the preview content
|
// render the preview content
|
||||||
let rp = build_preview_paragraph(&preview_state.preview.content);
|
let rp = build_preview_paragraph(
|
||||||
|
preview_state,
|
||||||
|
colorscheme.preview.highlight_bg,
|
||||||
|
);
|
||||||
f.render_widget(rp, inner);
|
f.render_widget(rp, inner);
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_preview_paragraph(content: &str) -> Paragraph<'_> {
|
pub fn build_preview_paragraph(
|
||||||
|
preview_state: &PreviewState,
|
||||||
|
highlight_bg: Color,
|
||||||
|
) -> Paragraph<'_> {
|
||||||
let preview_block =
|
let preview_block =
|
||||||
Block::default().style(Style::default()).padding(Padding {
|
Block::default().style(Style::default()).padding(Padding {
|
||||||
top: 0,
|
top: 0,
|
||||||
@ -47,14 +53,30 @@ pub fn build_preview_paragraph(content: &str) -> Paragraph<'_> {
|
|||||||
left: 1,
|
left: 1,
|
||||||
});
|
});
|
||||||
|
|
||||||
build_ansi_text_paragraph(content, preview_block)
|
build_ansi_text_paragraph(
|
||||||
|
&preview_state.preview.content,
|
||||||
|
preview_block,
|
||||||
|
preview_state.target_line,
|
||||||
|
highlight_bg,
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn build_ansi_text_paragraph<'a>(
|
fn build_ansi_text_paragraph<'a>(
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
preview_block: Block<'a>,
|
preview_block: Block<'a>,
|
||||||
|
target_line: Option<u16>,
|
||||||
|
highlight_bg: Color,
|
||||||
) -> Paragraph<'a> {
|
) -> Paragraph<'a> {
|
||||||
Paragraph::new(text.into_text().unwrap()).block(preview_block)
|
let mut t = text.into_text().unwrap();
|
||||||
|
if let Some(target_line) = target_line {
|
||||||
|
// Highlight the target line
|
||||||
|
if let Some(line) = t.lines.get_mut((target_line - 1) as usize) {
|
||||||
|
for span in &mut line.spans {
|
||||||
|
span.style = span.style.bg(highlight_bg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Paragraph::new(t).block(preview_block)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn build_meta_preview_paragraph<'a>(
|
pub fn build_meta_preview_paragraph<'a>(
|
||||||
|
@ -31,6 +31,7 @@ use std::collections::HashSet;
|
|||||||
use tokio::sync::mpsc::{
|
use tokio::sync::mpsc::{
|
||||||
unbounded_channel, UnboundedReceiver, UnboundedSender,
|
unbounded_channel, UnboundedReceiver, UnboundedSender,
|
||||||
};
|
};
|
||||||
|
use tracing::debug;
|
||||||
|
|
||||||
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
|
#[derive(PartialEq, Copy, Clone, Hash, Eq, Debug, Serialize, Deserialize)]
|
||||||
pub enum Mode {
|
pub enum Mode {
|
||||||
@ -72,7 +73,7 @@ impl Television {
|
|||||||
#[must_use]
|
#[must_use]
|
||||||
pub fn new(
|
pub fn new(
|
||||||
action_tx: UnboundedSender<Action>,
|
action_tx: UnboundedSender<Action>,
|
||||||
channel_prototype: ChannelPrototype,
|
channel_prototype: &ChannelPrototype,
|
||||||
mut config: Config,
|
mut config: Config,
|
||||||
input: Option<String>,
|
input: Option<String>,
|
||||||
no_remote: bool,
|
no_remote: bool,
|
||||||
@ -86,9 +87,9 @@ impl Television {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// previewer
|
// previewer
|
||||||
let preview_handles = Self::setup_previewer(&channel_prototype);
|
let preview_handles = Self::setup_previewer(channel_prototype);
|
||||||
|
|
||||||
let mut channel: CableChannel = channel_prototype.into();
|
let mut channel = CableChannel::new(channel_prototype);
|
||||||
|
|
||||||
let app_metadata = AppMetadata::new(
|
let app_metadata = AppMetadata::new(
|
||||||
env!("CARGO_PKG_VERSION").to_string(),
|
env!("CARGO_PKG_VERSION").to_string(),
|
||||||
@ -157,7 +158,7 @@ impl Television {
|
|||||||
let (pv_request_tx, pv_request_rx) = unbounded_channel();
|
let (pv_request_tx, pv_request_rx) = unbounded_channel();
|
||||||
let (pv_preview_tx, pv_preview_rx) = unbounded_channel();
|
let (pv_preview_tx, pv_preview_rx) = unbounded_channel();
|
||||||
let previewer = Previewer::new(
|
let previewer = Previewer::new(
|
||||||
channel_prototype.preview_command().unwrap(),
|
channel_prototype.preview_command.clone().unwrap(),
|
||||||
PreviewerConfig::default(),
|
PreviewerConfig::default(),
|
||||||
pv_request_rx,
|
pv_request_rx,
|
||||||
pv_preview_tx,
|
pv_preview_tx,
|
||||||
@ -204,7 +205,7 @@ impl Television {
|
|||||||
self.channel.name.clone()
|
self.channel.name.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn change_channel(&mut self, channel_prototype: ChannelPrototype) {
|
pub fn change_channel(&mut self, channel_prototype: &ChannelPrototype) {
|
||||||
self.preview_state.reset();
|
self.preview_state.reset();
|
||||||
self.preview_state.enabled =
|
self.preview_state.enabled =
|
||||||
channel_prototype.preview_command.is_some();
|
channel_prototype.preview_command.is_some();
|
||||||
@ -217,8 +218,9 @@ impl Television {
|
|||||||
.send(PreviewRequest::Shutdown)
|
.send(PreviewRequest::Shutdown)
|
||||||
.expect("Failed to send shutdown signal to previewer");
|
.expect("Failed to send shutdown signal to previewer");
|
||||||
}
|
}
|
||||||
self.preview_handles = Self::setup_previewer(&channel_prototype);
|
self.preview_handles = Self::setup_previewer(channel_prototype);
|
||||||
self.channel = channel_prototype.into();
|
self.channel = CableChannel::new(channel_prototype);
|
||||||
|
debug!("Changed channel to {:?}", channel_prototype);
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn find(&mut self, pattern: &str) {
|
pub fn find(&mut self, pattern: &str) {
|
||||||
@ -416,25 +418,26 @@ impl Television {
|
|||||||
// available previews
|
// available previews
|
||||||
let entry = selected_entry.as_ref().unwrap();
|
let entry = selected_entry.as_ref().unwrap();
|
||||||
if let Ok(preview) = receiver.try_recv() {
|
if let Ok(preview) = receiver.try_recv() {
|
||||||
|
let scroll = entry
|
||||||
|
.line_number
|
||||||
|
.unwrap_or(0)
|
||||||
|
.saturating_sub(
|
||||||
|
(self
|
||||||
|
.ui_state
|
||||||
|
.layout
|
||||||
|
.preview_window
|
||||||
|
.map_or(0, |w| w.height.saturating_sub(2)) // borders
|
||||||
|
/ 2)
|
||||||
|
.into(),
|
||||||
|
)
|
||||||
|
.saturating_add(3) // 3 lines above the center
|
||||||
|
.try_into()
|
||||||
|
// if the scroll doesn't fit in a u16, just scroll to the top
|
||||||
|
// this is a current limitation of ratatui
|
||||||
|
.unwrap_or(0);
|
||||||
self.preview_state.update(
|
self.preview_state.update(
|
||||||
preview,
|
preview,
|
||||||
// scroll to center the selected entry
|
scroll,
|
||||||
entry
|
|
||||||
.line_number
|
|
||||||
.unwrap_or(0)
|
|
||||||
.saturating_sub(
|
|
||||||
(self
|
|
||||||
.ui_state
|
|
||||||
.layout
|
|
||||||
.preview_window
|
|
||||||
.map_or(0, |w| w.height)
|
|
||||||
/ 2)
|
|
||||||
.into(),
|
|
||||||
)
|
|
||||||
.try_into()
|
|
||||||
// if the scroll doesn't fit in a u16, just scroll to the top
|
|
||||||
// this is a current limitation of ratatui
|
|
||||||
.unwrap_or(0),
|
|
||||||
entry.line_number.and_then(|l| l.try_into().ok()),
|
entry.line_number.and_then(|l| l.try_into().ok()),
|
||||||
);
|
);
|
||||||
self.action_tx.send(Action::Render)?;
|
self.action_tx.send(Action::Render)?;
|
||||||
@ -547,7 +550,7 @@ impl Television {
|
|||||||
self.reset_picker_input();
|
self.reset_picker_input();
|
||||||
self.remote_control.as_mut().unwrap().find(EMPTY_STRING);
|
self.remote_control.as_mut().unwrap().find(EMPTY_STRING);
|
||||||
self.mode = Mode::Channel;
|
self.mode = Mode::Channel;
|
||||||
self.change_channel(new_channel);
|
self.change_channel(&new_channel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
14
tests/app.rs
14
tests/app.rs
@ -47,7 +47,7 @@ fn setup_app(
|
|||||||
false,
|
false,
|
||||||
config.application.tick_rate,
|
config.application.tick_rate,
|
||||||
);
|
);
|
||||||
let mut app = App::new(chan, config, input, options, &Cable::default());
|
let mut app = App::new(&chan, config, input, options, &Cable::default());
|
||||||
|
|
||||||
// retrieve the app's action channel handle in order to send a quit action
|
// retrieve the app's action channel handle in order to send a quit action
|
||||||
let tx = app.action_tx.clone();
|
let tx = app.action_tx.clone();
|
||||||
@ -212,14 +212,8 @@ async fn test_app_exact_search_positive() {
|
|||||||
|
|
||||||
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
|
#[tokio::test(flavor = "multi_thread", worker_threads = 3)]
|
||||||
async fn test_app_exits_when_select_1_and_only_one_result() {
|
async fn test_app_exits_when_select_1_and_only_one_result() {
|
||||||
let prototype = ChannelPrototype::new(
|
let prototype =
|
||||||
"some_channel",
|
ChannelPrototype::new("some_channel", "echo file1.txt", false, None);
|
||||||
"echo file1.txt",
|
|
||||||
false,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
|
||||||
let (f, tx) = setup_app(Some(prototype), true, false);
|
let (f, tx) = setup_app(Some(prototype), true, false);
|
||||||
|
|
||||||
// tick a few times to get the results
|
// tick a few times to get the results
|
||||||
@ -255,8 +249,6 @@ async fn test_app_does_not_exit_when_select_1_and_more_than_one_result() {
|
|||||||
"echo 'file1.txt\nfile2.txt'",
|
"echo 'file1.txt\nfile2.txt'",
|
||||||
false,
|
false,
|
||||||
None,
|
None,
|
||||||
None,
|
|
||||||
None,
|
|
||||||
);
|
);
|
||||||
let (f, tx) = setup_app(Some(prototype), true, false);
|
let (f, tx) = setup_app(Some(prototype), true, false);
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user