mirror of
https://github.com/denoland/deno.git
synced 2025-09-22 02:12:33 +00:00

Some checks are pending
ci / pre-build (push) Waiting to run
ci / bench release linux-x86_64 (push) Blocked by required conditions
ci / lint debug macos-x86_64 (push) Blocked by required conditions
ci / lint debug windows-x86_64 (push) Blocked by required conditions
ci / test debug linux-x86_64 (push) Blocked by required conditions
ci / test release linux-x86_64 (push) Blocked by required conditions
ci / test debug macos-x86_64 (push) Blocked by required conditions
ci / test release macos-x86_64 (push) Blocked by required conditions
ci / test debug windows-x86_64 (push) Blocked by required conditions
ci / test release windows-x86_64 (push) Blocked by required conditions
ci / build libs (push) Blocked by required conditions
ci / publish canary (push) Blocked by required conditions
ci / test debug linux-aarch64 (push) Blocked by required conditions
ci / test release linux-aarch64 (push) Blocked by required conditions
ci / test debug macos-aarch64 (push) Blocked by required conditions
ci / test release macos-aarch64 (push) Blocked by required conditions
ci / lint debug linux-x86_64 (push) Blocked by required conditions
When running `deno fmt` with no input paths with no deno.json or package.json: * In tty environments, prompts for confirmation. * In non-tty environments, errors without providing the current directory (`deno fmt .`). The reason for this is we had too many complaints of people accidentally running `deno fmt` in a directory that wasn't a JS project.
160 lines
3.8 KiB
Rust
160 lines
3.8 KiB
Rust
// Copyright 2018-2025 the Deno authors. MIT license.
|
|
|
|
use std::io;
|
|
use std::sync::Arc;
|
|
|
|
use crossterm::ExecutableCommand;
|
|
use crossterm::cursor;
|
|
use crossterm::event::KeyCode;
|
|
use crossterm::event::KeyEvent;
|
|
use crossterm::event::KeyEventKind;
|
|
use crossterm::event::KeyModifiers;
|
|
use crossterm::terminal;
|
|
use deno_core::parking_lot::Mutex;
|
|
use deno_runtime::ops::tty::ConsoleSize;
|
|
|
|
use super::draw_thread::DrawThread;
|
|
|
|
/// Gets the console size.
|
|
pub fn console_size() -> Option<ConsoleSize> {
|
|
let stderr = &deno_runtime::deno_io::STDERR_HANDLE;
|
|
deno_runtime::ops::tty::console_size(stderr).ok()
|
|
}
|
|
|
|
pub struct RawMode {
|
|
needs_disable: bool,
|
|
}
|
|
|
|
impl RawMode {
|
|
pub fn enable() -> io::Result<Self> {
|
|
terminal::enable_raw_mode()?;
|
|
Ok(Self {
|
|
needs_disable: true,
|
|
})
|
|
}
|
|
|
|
pub fn disable(mut self) -> io::Result<()> {
|
|
self.needs_disable = false;
|
|
terminal::disable_raw_mode()
|
|
}
|
|
}
|
|
|
|
impl Drop for RawMode {
|
|
fn drop(&mut self) {
|
|
if self.needs_disable {
|
|
let _ = terminal::disable_raw_mode();
|
|
}
|
|
}
|
|
}
|
|
|
|
pub struct HideCursorGuard {
|
|
needs_disable: bool,
|
|
}
|
|
|
|
impl HideCursorGuard {
|
|
pub fn hide() -> io::Result<Self> {
|
|
io::stderr().execute(cursor::Hide)?;
|
|
Ok(Self {
|
|
needs_disable: true,
|
|
})
|
|
}
|
|
|
|
pub fn show(mut self) -> io::Result<()> {
|
|
self.needs_disable = false;
|
|
io::stderr().execute(cursor::Show)?;
|
|
Ok(())
|
|
}
|
|
}
|
|
|
|
impl Drop for HideCursorGuard {
|
|
fn drop(&mut self) {
|
|
if self.needs_disable {
|
|
_ = io::stderr().execute(cursor::Show);
|
|
}
|
|
}
|
|
}
|
|
|
|
#[derive(Debug)]
|
|
pub struct ConfirmOptions {
|
|
pub message: String,
|
|
pub default: bool,
|
|
}
|
|
|
|
/// Prompts and confirms if a tty.
|
|
///
|
|
/// Returns `None` when a tty.
|
|
pub fn confirm(options: ConfirmOptions) -> Option<bool> {
|
|
#[derive(Debug)]
|
|
struct PromptRenderer {
|
|
options: ConfirmOptions,
|
|
selection: Arc<Mutex<String>>,
|
|
}
|
|
|
|
impl super::draw_thread::DrawThreadRenderer for PromptRenderer {
|
|
fn render(&self, _data: &ConsoleSize) -> String {
|
|
let is_yes_default = self.options.default;
|
|
let selection = self.selection.lock();
|
|
format!(
|
|
"{} [{}/{}] {}",
|
|
self.options.message,
|
|
if is_yes_default { "Y" } else { "y" },
|
|
if is_yes_default { "n" } else { "N" },
|
|
*selection,
|
|
)
|
|
}
|
|
}
|
|
|
|
if !DrawThread::is_supported() {
|
|
return None;
|
|
}
|
|
|
|
let _raw_mode = RawMode::enable().ok()?;
|
|
let _hide_cursor_guard = HideCursorGuard::hide().ok()?;
|
|
let selection = Arc::new(Mutex::new(String::new()));
|
|
let default = options.default;
|
|
// uses a renderer and the draw thread in order to allow
|
|
// displaying other stuff on the draw thread while the prompt
|
|
// is showing
|
|
let renderer = PromptRenderer {
|
|
options,
|
|
selection: selection.clone(),
|
|
};
|
|
let _state = DrawThread::add_entry(Arc::new(renderer));
|
|
|
|
let mut selected = default;
|
|
loop {
|
|
let event = crossterm::event::read().ok()?;
|
|
#[allow(clippy::single_match)]
|
|
match event {
|
|
crossterm::event::Event::Key(KeyEvent {
|
|
kind: KeyEventKind::Press,
|
|
code,
|
|
modifiers,
|
|
..
|
|
}) => match (code, modifiers) {
|
|
(KeyCode::Char('c'), KeyModifiers::CONTROL)
|
|
| (KeyCode::Char('q'), KeyModifiers::NONE) => break,
|
|
(KeyCode::Char('y'), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
|
|
selected = true;
|
|
*selection.lock() = "Y".to_string();
|
|
}
|
|
(KeyCode::Char('n'), KeyModifiers::NONE | KeyModifiers::SHIFT) => {
|
|
selected = false;
|
|
*selection.lock() = "N".to_string();
|
|
}
|
|
(KeyCode::Backspace, _) => {
|
|
selected = default;
|
|
*selection.lock() = "".to_string();
|
|
}
|
|
// l is common for enter in vim keybindings
|
|
(KeyCode::Enter, _) | (KeyCode::Char('l'), KeyModifiers::NONE) => {
|
|
return Some(selected);
|
|
}
|
|
_ => {}
|
|
},
|
|
_ => {}
|
|
}
|
|
}
|
|
|
|
None
|
|
}
|