mirror of
https://github.com/denoland/deno.git
synced 2025-02-01 12:16:11 -05:00
use console_static_text and deno_terminal
This commit is contained in:
parent
e39f145fba
commit
bc3f783d98
4 changed files with 165 additions and 168 deletions
18
Cargo.lock
generated
18
Cargo.lock
generated
|
@ -970,12 +970,12 @@ dependencies = [
|
|||
|
||||
[[package]]
|
||||
name = "console_static_text"
|
||||
version = "0.8.1"
|
||||
version = "0.8.3"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f4be93df536dfbcbd39ff7c129635da089901116b88bfc29ec1acb9b56f8ff35"
|
||||
checksum = "55d8a913e62f6444b79e038be3eb09839e9cfc34d55d85f9336460710647d2f6"
|
||||
dependencies = [
|
||||
"unicode-width",
|
||||
"vte",
|
||||
"vte 0.13.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -7427,7 +7427,7 @@ version = "0.2.0"
|
|||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "55ff8ef943b384c414f54aefa961dd2bd853add74ec75e7ac74cf91dba62bcfa"
|
||||
dependencies = [
|
||||
"vte",
|
||||
"vte 0.11.1",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
@ -8804,6 +8804,16 @@ name = "vte"
|
|||
version = "0.11.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "f5022b5fbf9407086c180e9557be968742d839e68346af7792b8592489732197"
|
||||
dependencies = [
|
||||
"utf8parse",
|
||||
"vte_generate_state_changes",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "vte"
|
||||
version = "0.13.1"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "9a0b683b20ef64071ff03745b14391751f6beab06a54347885459b77a3f2caa5"
|
||||
dependencies = [
|
||||
"arrayvec",
|
||||
"utf8parse",
|
||||
|
|
|
@ -123,7 +123,7 @@ cbc = { version = "=0.1.2", features = ["alloc"] }
|
|||
# Instead use util::time::utc_now()
|
||||
chrono = { version = "0.4", default-features = false, features = ["std", "serde"] }
|
||||
color-print = "0.3.5"
|
||||
console_static_text = "=0.8.1"
|
||||
console_static_text = "=0.8.3"
|
||||
ctr = { version = "0.9.2", features = ["alloc"] }
|
||||
dashmap = "5.5.3"
|
||||
data-encoding = "2.3.3"
|
||||
|
|
|
@ -350,14 +350,10 @@ async fn update(
|
|||
interactive::PackageInfo {
|
||||
current_version: current_version
|
||||
.as_ref()
|
||||
.map(|nv| nv.version.to_string())
|
||||
.unwrap_or_default(),
|
||||
.map(|nv| nv.version.clone()),
|
||||
name: dep.alias_or_name().into(),
|
||||
kind: dep.kind,
|
||||
new_version: new_req
|
||||
.version_text()
|
||||
.trim_start_matches('^')
|
||||
.to_string(),
|
||||
new_version: new_req.clone(),
|
||||
}
|
||||
},
|
||||
)
|
||||
|
|
|
@ -1,53 +1,101 @@
|
|||
// Copyright 2018-2025 the Deno authors. MIT license.
|
||||
|
||||
use std::collections::HashSet;
|
||||
use std::fmt::Write as _;
|
||||
use std::io;
|
||||
use std::io::Write;
|
||||
|
||||
use console_static_text::ConsoleSize;
|
||||
use console_static_text::TextItem;
|
||||
use crossterm::cursor;
|
||||
use crossterm::event::KeyCode;
|
||||
use crossterm::event::KeyEvent;
|
||||
use crossterm::event::KeyEventKind;
|
||||
use crossterm::event::KeyModifiers;
|
||||
use crossterm::style;
|
||||
use crossterm::style::Stylize;
|
||||
use crossterm::terminal;
|
||||
use crossterm::ExecutableCommand;
|
||||
use crossterm::QueueableCommand;
|
||||
use deno_core::anyhow;
|
||||
use deno_core::anyhow::Context;
|
||||
use deno_semver::Version;
|
||||
use deno_semver::VersionReq;
|
||||
use deno_terminal::colors;
|
||||
|
||||
use crate::tools::registry::pm::deps::DepKind;
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct PackageInfo {
|
||||
pub current_version: String,
|
||||
pub new_version: String,
|
||||
pub current_version: Option<Version>,
|
||||
pub new_version: VersionReq,
|
||||
pub name: String,
|
||||
pub kind: DepKind,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct FormattedPackageInfo {
|
||||
current_version_string: Option<String>,
|
||||
new_version_highlighted: String,
|
||||
formatted_name: String,
|
||||
formatted_name_len: usize,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct State {
|
||||
packages: Vec<PackageInfo>,
|
||||
packages: Vec<FormattedPackageInfo>,
|
||||
currently_selected: usize,
|
||||
checked: HashSet<usize>,
|
||||
|
||||
name_width: usize,
|
||||
current_width: usize,
|
||||
start_row: u16,
|
||||
// start_row: u16,
|
||||
}
|
||||
|
||||
impl From<PackageInfo> for FormattedPackageInfo {
|
||||
fn from(package: PackageInfo) -> Self {
|
||||
let new_version_string =
|
||||
package.new_version.version_text().trim_start_matches('^');
|
||||
|
||||
let new_version_highlighted =
|
||||
if let (Some(current_version), Ok(new_version)) = (
|
||||
&package.current_version,
|
||||
Version::parse_standard(new_version_string),
|
||||
) {
|
||||
highlight_new_version(current_version, &new_version)
|
||||
} else {
|
||||
new_version_string.to_string()
|
||||
};
|
||||
FormattedPackageInfo {
|
||||
current_version_string: package
|
||||
.current_version
|
||||
.as_ref()
|
||||
.map(|v| v.to_string()),
|
||||
new_version_highlighted,
|
||||
formatted_name: format!(
|
||||
"{}{}",
|
||||
colors::gray(format!("{}:", package.kind.scheme())),
|
||||
package.name
|
||||
),
|
||||
formatted_name_len: package.kind.scheme().len() + 1 + package.name.len(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl State {
|
||||
fn new(packages: Vec<PackageInfo>) -> anyhow::Result<Self> {
|
||||
let packages: Vec<_> = packages
|
||||
.into_iter()
|
||||
.map(FormattedPackageInfo::from)
|
||||
.collect();
|
||||
let name_width = packages
|
||||
.iter()
|
||||
.map(|p| p.name.len() + p.kind.scheme().len() + 1)
|
||||
.map(|p| p.formatted_name_len)
|
||||
.max()
|
||||
.unwrap_or_default();
|
||||
let current_width = packages
|
||||
.iter()
|
||||
.map(|p| p.current_version.len())
|
||||
.map(|p| {
|
||||
p.current_version_string
|
||||
.as_ref()
|
||||
.map(|s| s.len())
|
||||
.unwrap_or_default()
|
||||
})
|
||||
.max()
|
||||
.unwrap_or_default();
|
||||
|
||||
|
@ -58,77 +106,52 @@ impl State {
|
|||
|
||||
name_width,
|
||||
current_width,
|
||||
start_row: cursor::position()?.1,
|
||||
})
|
||||
}
|
||||
|
||||
fn render<W: std::io::Write>(&self, out: &mut W) -> anyhow::Result<()> {
|
||||
use cursor::MoveTo;
|
||||
use style::Print;
|
||||
use style::PrintStyledContent;
|
||||
fn render(&self) -> anyhow::Result<Vec<TextItem>> {
|
||||
let mut items = Vec::with_capacity(self.packages.len() + 1);
|
||||
|
||||
crossterm::queue!(
|
||||
out,
|
||||
MoveTo(0, self.start_row),
|
||||
terminal::Clear(terminal::ClearType::FromCursorDown),
|
||||
PrintStyledContent("?".blue()),
|
||||
Print(" Select which packages to update (<space> to select, ↑/↓/j/k to navigate, enter to accept, <Ctrl-c> to cancel)")
|
||||
)?;
|
||||
|
||||
let base = self.start_row + 1;
|
||||
items.push(TextItem::new_owned(format!(
|
||||
"{} Select which packages to update (<space> to select, ↑/↓/j/k to navigate, enter to accept, <Ctrl-c> to cancel)",
|
||||
colors::intense_blue("?")
|
||||
)));
|
||||
|
||||
for (i, package) in self.packages.iter().enumerate() {
|
||||
if self.currently_selected == i {
|
||||
crossterm::queue!(
|
||||
out,
|
||||
MoveTo(0, base + (self.currently_selected as u16)),
|
||||
PrintStyledContent("❯".blue()),
|
||||
Print(' '),
|
||||
)?;
|
||||
}
|
||||
let mut line = String::new();
|
||||
let f = &mut line;
|
||||
|
||||
let checked = self.checked.contains(&i);
|
||||
let selector = if checked { "●" } else { "○" };
|
||||
crossterm::queue!(
|
||||
out,
|
||||
MoveTo(2, base + (i as u16)),
|
||||
Print(selector),
|
||||
Print(" "),
|
||||
write!(
|
||||
f,
|
||||
"{} {} ",
|
||||
if self.currently_selected == i {
|
||||
colors::intense_blue("❯").to_string()
|
||||
} else {
|
||||
" ".to_string()
|
||||
},
|
||||
if checked { "●" } else { "○" }
|
||||
)?;
|
||||
|
||||
if self.currently_selected == i {
|
||||
out.queue(style::SetStyle(
|
||||
style::ContentStyle::new().on_black().white().bold(),
|
||||
))?;
|
||||
}
|
||||
let want = &package.new_version;
|
||||
let new_version_highlight =
|
||||
highlight_new_version(&package.current_version, want)?;
|
||||
let formatted_name = format!(
|
||||
"{}{}",
|
||||
deno_terminal::colors::gray(format!("{}:", package.kind.scheme())),
|
||||
package.name
|
||||
);
|
||||
let name_pad = " ".repeat(self.name_width + 2 - (package.name.len() + 4));
|
||||
|
||||
crossterm::queue!(
|
||||
out,
|
||||
Print(format!(
|
||||
"{formatted_name}{name_pad} {:<current_width$} -> {}",
|
||||
package.current_version,
|
||||
new_version_highlight,
|
||||
current_width = self.current_width
|
||||
)),
|
||||
let name_pad =
|
||||
" ".repeat(self.name_width + 2 - package.formatted_name_len);
|
||||
write!(
|
||||
f,
|
||||
"{formatted_name}{name_pad} {:<current_width$} -> {}",
|
||||
package
|
||||
.current_version_string
|
||||
.as_deref()
|
||||
.unwrap_or_default(),
|
||||
&package.new_version_highlighted,
|
||||
name_pad = name_pad,
|
||||
formatted_name = package.formatted_name,
|
||||
current_width = self.current_width
|
||||
)?;
|
||||
if self.currently_selected == i {
|
||||
out.queue(style::ResetColor)?;
|
||||
}
|
||||
|
||||
items.push(TextItem::with_hanging_indent_owned(line, 1));
|
||||
}
|
||||
|
||||
out.queue(MoveTo(0, base + self.packages.len() as u16))?;
|
||||
|
||||
out.flush()?;
|
||||
|
||||
Ok(())
|
||||
Ok(items)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -138,45 +161,7 @@ enum VersionDifference {
|
|||
Patch,
|
||||
}
|
||||
|
||||
struct VersionParts {
|
||||
major: u64,
|
||||
minor: u64,
|
||||
patch: u64,
|
||||
pre: Option<String>,
|
||||
}
|
||||
|
||||
impl VersionParts {
|
||||
fn parse(s: &str) -> Result<VersionParts, anyhow::Error> {
|
||||
let mut parts = s.splitn(3, '.');
|
||||
let major = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("expected major version"))?
|
||||
.parse()?;
|
||||
let minor = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("expected minor version"))?
|
||||
.parse()?;
|
||||
let patch = parts
|
||||
.next()
|
||||
.ok_or_else(|| anyhow::anyhow!("expected patch version"))?;
|
||||
let (patch, pre) = if patch.contains('-') {
|
||||
let (patch, pre) = patch.split_once('-').unwrap();
|
||||
(patch, Some(pre.into()))
|
||||
} else {
|
||||
(patch, None)
|
||||
};
|
||||
let patch = patch.parse()?;
|
||||
let pre = pre.clone();
|
||||
Ok(Self {
|
||||
patch,
|
||||
pre,
|
||||
minor,
|
||||
major,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn version_diff(a: &VersionParts, b: &VersionParts) -> VersionDifference {
|
||||
fn version_diff(a: &Version, b: &Version) -> VersionDifference {
|
||||
if a.major != b.major {
|
||||
VersionDifference::Major
|
||||
} else if a.minor != b.minor {
|
||||
|
@ -186,48 +171,43 @@ fn version_diff(a: &VersionParts, b: &VersionParts) -> VersionDifference {
|
|||
}
|
||||
}
|
||||
|
||||
fn highlight_new_version(
|
||||
current: &str,
|
||||
new: &str,
|
||||
) -> Result<String, anyhow::Error> {
|
||||
let current_parts = VersionParts::parse(current)
|
||||
.with_context(|| format!("parsing current version: {current}"))?;
|
||||
let new_parts = VersionParts::parse(new)
|
||||
.with_context(|| format!("parsing new version: {new}"))?;
|
||||
let diff = version_diff(¤t_parts, &new_parts);
|
||||
fn highlight_new_version(current: &Version, new: &Version) -> String {
|
||||
let diff = version_diff(current, new);
|
||||
|
||||
Ok(match diff {
|
||||
let new_pre = if new.pre.is_empty() {
|
||||
String::new()
|
||||
} else {
|
||||
let mut s = String::new();
|
||||
s.push('-');
|
||||
for p in &new.pre {
|
||||
s.push_str(p);
|
||||
}
|
||||
s
|
||||
};
|
||||
|
||||
match diff {
|
||||
VersionDifference::Major => format!(
|
||||
"{}.{}.{}{}",
|
||||
style::style(new_parts.major).red().bold(),
|
||||
style::style(new_parts.minor).red(),
|
||||
style::style(new_parts.patch).red(),
|
||||
new_parts
|
||||
.pre
|
||||
.map(|pre| pre.red().to_string())
|
||||
.unwrap_or_default()
|
||||
colors::red_bold(new.major),
|
||||
colors::red(new.minor),
|
||||
colors::red(new.patch),
|
||||
colors::red(new_pre)
|
||||
),
|
||||
VersionDifference::Minor => format!(
|
||||
"{}.{}.{}{}",
|
||||
new_parts.major,
|
||||
style::style(new_parts.minor).yellow().bold(),
|
||||
style::style(new_parts.patch).yellow(),
|
||||
new_parts
|
||||
.pre
|
||||
.map(|pre| pre.yellow().to_string())
|
||||
.unwrap_or_default()
|
||||
new.major,
|
||||
colors::yellow_bold(new.minor),
|
||||
colors::yellow(new.patch),
|
||||
colors::yellow(new_pre)
|
||||
),
|
||||
VersionDifference::Patch => format!(
|
||||
"{}.{}.{}{}",
|
||||
new_parts.major,
|
||||
new_parts.minor,
|
||||
style::style(new_parts.patch).green().bold(),
|
||||
new_parts
|
||||
.pre
|
||||
.map(|pre| pre.green().to_string())
|
||||
.unwrap_or_default()
|
||||
new.major,
|
||||
new.minor,
|
||||
colors::green_bold(new.patch),
|
||||
colors::green(new_pre)
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
struct RawMode {
|
||||
|
@ -258,26 +238,42 @@ impl Drop for RawMode {
|
|||
pub fn select_interactive(
|
||||
packages: Vec<PackageInfo>,
|
||||
) -> anyhow::Result<Option<HashSet<usize>>> {
|
||||
let mut stdout = io::stdout();
|
||||
let raw_mode = RawMode::enable()?;
|
||||
let mut stderr = io::stderr();
|
||||
|
||||
let (_, rows) = terminal::size()?;
|
||||
let raw_mode = RawMode::enable()?;
|
||||
let mut static_text =
|
||||
console_static_text::ConsoleStaticText::new(move || {
|
||||
if let Ok((cols, rows)) = terminal::size() {
|
||||
ConsoleSize {
|
||||
cols: Some(cols),
|
||||
rows: Some(rows),
|
||||
}
|
||||
} else {
|
||||
ConsoleSize {
|
||||
cols: None,
|
||||
rows: None,
|
||||
}
|
||||
}
|
||||
});
|
||||
static_text.keep_cursor_zero_column(true);
|
||||
|
||||
let (_, start_row) = cursor::position().unwrap_or_default();
|
||||
let (_, rows) = terminal::size()?;
|
||||
if rows - start_row < (packages.len() + 2) as u16 {
|
||||
let pad = ((packages.len() + 2) as u16) - (rows - start_row);
|
||||
|
||||
stdout.execute(terminal::ScrollUp(pad))?;
|
||||
stdout.execute(cursor::MoveUp(pad))?;
|
||||
stderr.execute(terminal::ScrollUp(pad))?;
|
||||
stderr.execute(cursor::MoveUp(pad))?;
|
||||
}
|
||||
|
||||
let mut state = State::new(packages)?;
|
||||
stdout.execute(cursor::Hide)?;
|
||||
|
||||
state.render(&mut stdout)?;
|
||||
stderr.execute(cursor::Hide)?;
|
||||
|
||||
let mut do_it = false;
|
||||
loop {
|
||||
let items = state.render()?;
|
||||
static_text.eprint_items(items.iter());
|
||||
|
||||
let event = crossterm::event::read()?;
|
||||
#[allow(clippy::single_match)]
|
||||
match event {
|
||||
|
@ -312,16 +308,11 @@ pub fn select_interactive(
|
|||
},
|
||||
_ => {}
|
||||
}
|
||||
state.render(&mut stdout)?;
|
||||
}
|
||||
|
||||
crossterm::queue!(
|
||||
&mut stdout,
|
||||
cursor::MoveTo(0, state.start_row),
|
||||
terminal::Clear(terminal::ClearType::FromCursorDown),
|
||||
cursor::Show,
|
||||
)?;
|
||||
stdout.flush()?;
|
||||
static_text.eprint_clear();
|
||||
|
||||
crossterm::execute!(&mut stderr, cursor::Show)?;
|
||||
|
||||
raw_mode.disable()?;
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue