fix: restore cursor after inline terminal menus
This commit is contained in:
@@ -7,15 +7,15 @@ use client::{
|
|||||||
list_backend_workers, restore_backend_worker,
|
list_backend_workers, restore_backend_worker,
|
||||||
};
|
};
|
||||||
use crossterm::event::{self, Event as TermEvent, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event as TermEvent, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::Frame;
|
||||||
use ratatui::layout::{Constraint, Layout};
|
use ratatui::layout::{Constraint, Layout};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::{Frame, Terminal, TerminalOptions, Viewport};
|
|
||||||
|
|
||||||
use crate::backend_workspace_picker::select_backend_workspace;
|
use crate::backend_workspace_picker::select_backend_workspace;
|
||||||
use crate::console;
|
use crate::console;
|
||||||
|
use crate::inline_terminal::with_inline_terminal;
|
||||||
|
|
||||||
const MAX_ROWS: usize = 10;
|
const MAX_ROWS: usize = 10;
|
||||||
const VIEWPORT_LINES: u16 = MAX_ROWS as u16 + 4;
|
const VIEWPORT_LINES: u16 = MAX_ROWS as u16 + 4;
|
||||||
@@ -127,31 +127,32 @@ fn pick_worker(
|
|||||||
workers.truncate(MAX_ROWS);
|
workers.truncate(MAX_ROWS);
|
||||||
|
|
||||||
let mut state = BackendWorkerPickerState::new(target, workers);
|
let mut state = BackendWorkerPickerState::new(target, workers);
|
||||||
let mut terminal = make_inline_terminal()?;
|
with_inline_terminal(
|
||||||
loop {
|
VIEWPORT_LINES,
|
||||||
terminal.draw(|frame| draw(frame, &state))?;
|
|terminal| -> Result<_, Box<dyn std::error::Error>> {
|
||||||
match poll_event()? {
|
loop {
|
||||||
None => continue,
|
terminal.draw(|frame| draw(frame, &state))?;
|
||||||
Some(Action::Up) => state.previous(),
|
match poll_event()? {
|
||||||
Some(Action::Down) => state.next(),
|
None => continue,
|
||||||
Some(Action::Submit) => {
|
Some(Action::Up) => state.previous(),
|
||||||
close_viewport(&mut terminal)?;
|
Some(Action::Down) => state.next(),
|
||||||
return Ok(WorkerPickerResult::Selected(
|
Some(Action::Submit) => {
|
||||||
state.selected_worker().clone(),
|
return Ok(WorkerPickerResult::Selected(
|
||||||
));
|
state.selected_worker().clone(),
|
||||||
|
));
|
||||||
|
}
|
||||||
|
Some(Action::SwitchWorkspace) => {
|
||||||
|
return Ok(WorkerPickerResult::SwitchWorkspace);
|
||||||
|
}
|
||||||
|
Some(Action::Cancel) => {
|
||||||
|
return Err(Box::new(io::Error::other(
|
||||||
|
"Backend worker picker cancelled",
|
||||||
|
)));
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Some(Action::SwitchWorkspace) => {
|
},
|
||||||
close_viewport(&mut terminal)?;
|
)
|
||||||
return Ok(WorkerPickerResult::SwitchWorkspace);
|
|
||||||
}
|
|
||||||
Some(Action::Cancel) => {
|
|
||||||
close_viewport(&mut terminal)?;
|
|
||||||
return Err(Box::new(io::Error::other(
|
|
||||||
"Backend worker picker cancelled",
|
|
||||||
)));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
struct BackendWorkerPickerState {
|
struct BackendWorkerPickerState {
|
||||||
@@ -184,27 +185,6 @@ impl BackendWorkerPickerState {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn make_inline_terminal() -> io::Result<Terminal<CrosstermBackend<io::Stdout>>> {
|
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
|
||||||
Terminal::with_options(
|
|
||||||
backend,
|
|
||||||
TerminalOptions {
|
|
||||||
viewport: Viewport::Inline(VIEWPORT_LINES),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn close_viewport(terminal: &mut Terminal<CrosstermBackend<io::Stdout>>) -> io::Result<()> {
|
|
||||||
let area = terminal.get_frame().area();
|
|
||||||
let last_row = area.bottom().saturating_sub(1);
|
|
||||||
terminal.set_cursor_position((0, last_row))?;
|
|
||||||
use std::io::Write;
|
|
||||||
let mut out = io::stdout();
|
|
||||||
out.write_all(b"\r\n")?;
|
|
||||||
out.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
enum Action {
|
enum Action {
|
||||||
Up,
|
Up,
|
||||||
Down,
|
Down,
|
||||||
|
|||||||
@@ -0,0 +1,109 @@
|
|||||||
|
use std::io::{self, Stdout, Write};
|
||||||
|
|
||||||
|
use ratatui::Terminal;
|
||||||
|
use ratatui::backend::CrosstermBackend;
|
||||||
|
use ratatui::{TerminalOptions, Viewport};
|
||||||
|
|
||||||
|
pub(crate) type InlineTerminal = Terminal<CrosstermBackend<Stdout>>;
|
||||||
|
|
||||||
|
struct InlineTerminalGuard {
|
||||||
|
terminal: InlineTerminal,
|
||||||
|
closed: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl InlineTerminalGuard {
|
||||||
|
fn open(height: u16) -> io::Result<Self> {
|
||||||
|
let terminal = Terminal::with_options(
|
||||||
|
CrosstermBackend::new(io::stdout()),
|
||||||
|
TerminalOptions {
|
||||||
|
viewport: Viewport::Inline(height),
|
||||||
|
},
|
||||||
|
)?;
|
||||||
|
Ok(Self {
|
||||||
|
terminal,
|
||||||
|
closed: false,
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn close(&mut self) -> io::Result<()> {
|
||||||
|
if self.closed {
|
||||||
|
return Ok(());
|
||||||
|
}
|
||||||
|
self.closed = true;
|
||||||
|
|
||||||
|
let area = self.terminal.get_frame().area();
|
||||||
|
let last_row = area.bottom().saturating_sub(1);
|
||||||
|
let cursor_result = self.terminal.set_cursor_position((0, last_row));
|
||||||
|
let output_result = write_viewport_terminator(&mut io::stdout());
|
||||||
|
cursor_result?;
|
||||||
|
output_result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Drop for InlineTerminalGuard {
|
||||||
|
fn drop(&mut self) {
|
||||||
|
let _ = self.close();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub(crate) fn with_inline_terminal<T, E>(
|
||||||
|
height: u16,
|
||||||
|
run: impl FnOnce(&mut InlineTerminal) -> Result<T, E>,
|
||||||
|
) -> Result<T, E>
|
||||||
|
where
|
||||||
|
E: From<io::Error>,
|
||||||
|
{
|
||||||
|
let mut guard = InlineTerminalGuard::open(height).map_err(E::from)?;
|
||||||
|
let result = run(&mut guard.terminal);
|
||||||
|
let close_result = guard.close();
|
||||||
|
match result {
|
||||||
|
Ok(value) => {
|
||||||
|
close_result.map_err(E::from)?;
|
||||||
|
Ok(value)
|
||||||
|
}
|
||||||
|
Err(error) => Err(error),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_viewport_terminator(output: &mut impl Write) -> io::Result<()> {
|
||||||
|
output.write_all(b"\r\n")?;
|
||||||
|
output.flush()
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn viewport_terminator_moves_following_output_to_a_fresh_line() {
|
||||||
|
let mut output = Vec::new();
|
||||||
|
|
||||||
|
write_viewport_terminator(&mut output).unwrap();
|
||||||
|
|
||||||
|
assert_eq!(output, b"\r\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn inline_viewport_construction_is_owned_by_this_module() {
|
||||||
|
fn assert_shared_owner(path: &std::path::Path) {
|
||||||
|
for entry in std::fs::read_dir(path).unwrap() {
|
||||||
|
let path = entry.unwrap().path();
|
||||||
|
if path.is_dir() {
|
||||||
|
assert_shared_owner(&path);
|
||||||
|
} else if path.extension().and_then(|value| value.to_str()) == Some("rs")
|
||||||
|
&& path.file_name().and_then(|value| value.to_str())
|
||||||
|
!= Some("inline_terminal.rs")
|
||||||
|
{
|
||||||
|
let source = std::fs::read_to_string(&path).unwrap();
|
||||||
|
assert!(
|
||||||
|
!source.contains("Viewport::Inline"),
|
||||||
|
"{} constructs an inline viewport outside its shared owner",
|
||||||
|
path.display()
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
assert_shared_owner(&std::path::Path::new(env!("CARGO_MANIFEST_DIR")).join("src"));
|
||||||
|
}
|
||||||
|
}
|
||||||
+5
-34
@@ -1,17 +1,17 @@
|
|||||||
use std::io::{self, Stdout, Write};
|
|
||||||
use std::process::ExitCode;
|
use std::process::ExitCode;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
use crossterm::terminal::{disable_raw_mode, enable_raw_mode};
|
||||||
use ratatui::backend::CrosstermBackend;
|
use ratatui::Frame;
|
||||||
use ratatui::layout::{Constraint, Layout};
|
use ratatui::layout::{Constraint, Layout};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::{Frame, Terminal, TerminalOptions, Viewport};
|
|
||||||
use secrets::{SecretStore, SecretValue};
|
use secrets::{SecretStore, SecretValue};
|
||||||
|
|
||||||
|
use crate::inline_terminal::{InlineTerminal, with_inline_terminal};
|
||||||
|
|
||||||
#[derive(Debug, Clone, PartialEq, Eq)]
|
#[derive(Debug, Clone, PartialEq, Eq)]
|
||||||
enum Mode {
|
enum Mode {
|
||||||
Normal,
|
Normal,
|
||||||
@@ -235,7 +235,6 @@ pub async fn launch() -> ExitCode {
|
|||||||
}
|
}
|
||||||
|
|
||||||
type UiResult<T> = Result<T, Box<dyn std::error::Error>>;
|
type UiResult<T> = Result<T, Box<dyn std::error::Error>>;
|
||||||
type InlineTerminal = Terminal<CrosstermBackend<Stdout>>;
|
|
||||||
|
|
||||||
const MAX_ROWS: usize = 10;
|
const MAX_ROWS: usize = 10;
|
||||||
const VIEWPORT_LINES: u16 = MAX_ROWS as u16 + 5;
|
const VIEWPORT_LINES: u16 = MAX_ROWS as u16 + 5;
|
||||||
@@ -270,37 +269,9 @@ impl Drop for RawModeGuard {
|
|||||||
fn run(store: SecretStore) -> UiResult<()> {
|
fn run(store: SecretStore) -> UiResult<()> {
|
||||||
enable_raw_mode()?;
|
enable_raw_mode()?;
|
||||||
let guard = RawModeGuard::new();
|
let guard = RawModeGuard::new();
|
||||||
let mut terminal = make_inline_terminal()?;
|
let result = with_inline_terminal(VIEWPORT_LINES, |terminal| run_loop(terminal, store));
|
||||||
let result = run_loop(&mut terminal, store);
|
|
||||||
let close_result = close_viewport(&mut terminal);
|
|
||||||
drop(terminal);
|
|
||||||
guard.restore();
|
guard.restore();
|
||||||
result?;
|
result
|
||||||
close_result?;
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn make_inline_terminal() -> io::Result<InlineTerminal> {
|
|
||||||
let backend = CrosstermBackend::new(io::stdout());
|
|
||||||
Terminal::with_options(
|
|
||||||
backend,
|
|
||||||
TerminalOptions {
|
|
||||||
viewport: Viewport::Inline(VIEWPORT_LINES),
|
|
||||||
},
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Park the cursor at the very bottom of the inline viewport and emit one
|
|
||||||
/// newline before dropping the terminal. This matches the resume picker and
|
|
||||||
/// keeps the shell prompt (or a later inline viewport) from drawing over rows.
|
|
||||||
fn close_viewport(terminal: &mut InlineTerminal) -> io::Result<()> {
|
|
||||||
let area = terminal.get_frame().area();
|
|
||||||
let last_row = area.bottom().saturating_sub(1);
|
|
||||||
terminal.set_cursor_position((0, last_row))?;
|
|
||||||
let mut out = io::stdout();
|
|
||||||
out.write_all(b"\r\n")?;
|
|
||||||
out.flush()?;
|
|
||||||
Ok(())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_loop(terminal: &mut InlineTerminal, store: SecretStore) -> UiResult<()> {
|
fn run_loop(terminal: &mut InlineTerminal, store: SecretStore) -> UiResult<()> {
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ mod composer_keys;
|
|||||||
mod console;
|
mod console;
|
||||||
#[cfg(feature = "e2e-test")]
|
#[cfg(feature = "e2e-test")]
|
||||||
mod e2e_observer;
|
mod e2e_observer;
|
||||||
|
mod inline_terminal;
|
||||||
mod input;
|
mod input;
|
||||||
pub mod keys;
|
pub mod keys;
|
||||||
mod markdown;
|
mod markdown;
|
||||||
|
|||||||
@@ -3,15 +3,14 @@ use std::time::Duration;
|
|||||||
|
|
||||||
use client::{StandaloneWorkerListIntent, StandaloneWorkerResumeIntent, Target};
|
use client::{StandaloneWorkerListIntent, StandaloneWorkerResumeIntent, Target};
|
||||||
use crossterm::event::{self, Event as TermEvent, KeyCode, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event as TermEvent, KeyCode, KeyEventKind, KeyModifiers};
|
||||||
use ratatui::Terminal;
|
|
||||||
use ratatui::backend::CrosstermBackend;
|
|
||||||
use ratatui::layout::{Constraint, Layout};
|
use ratatui::layout::{Constraint, Layout};
|
||||||
use ratatui::prelude::{Color, Line, Modifier, Span, Style};
|
use ratatui::prelude::{Color, Line, Modifier, Span, Style};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use ratatui::{TerminalOptions, Viewport};
|
|
||||||
use standalone::{StandaloneListScope, StandaloneWorkerRecord, StandaloneWorkerStore};
|
use standalone::{StandaloneListScope, StandaloneWorkerRecord, StandaloneWorkerStore};
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::inline_terminal::with_inline_terminal;
|
||||||
|
|
||||||
const LIMIT: usize = 100;
|
const LIMIT: usize = 100;
|
||||||
|
|
||||||
pub(crate) fn pick(
|
pub(crate) fn pick(
|
||||||
@@ -57,41 +56,36 @@ fn run_picker(
|
|||||||
records: Vec<StandaloneWorkerRecord>,
|
records: Vec<StandaloneWorkerRecord>,
|
||||||
) -> Result<Option<StandaloneWorkerRecord>, StandalonePickerError> {
|
) -> Result<Option<StandaloneWorkerRecord>, StandalonePickerError> {
|
||||||
let height = u16::try_from(records.len().saturating_add(3).min(20)).unwrap_or(20);
|
let height = u16::try_from(records.len().saturating_add(3).min(20)).unwrap_or(20);
|
||||||
let mut terminal = Terminal::with_options(
|
with_inline_terminal(height, |terminal| {
|
||||||
CrosstermBackend::new(io::stdout()),
|
let mut selected = 0usize;
|
||||||
TerminalOptions {
|
loop {
|
||||||
viewport: Viewport::Inline(height),
|
terminal
|
||||||
},
|
.draw(|frame| draw(frame, &records, selected))
|
||||||
)
|
.map_err(StandalonePickerError::Io)?;
|
||||||
.map_err(StandalonePickerError::Io)?;
|
if !event::poll(Duration::from_millis(100)).map_err(StandalonePickerError::Io)? {
|
||||||
let mut selected = 0usize;
|
continue;
|
||||||
loop {
|
|
||||||
terminal
|
|
||||||
.draw(|frame| draw(frame, &records, selected))
|
|
||||||
.map_err(StandalonePickerError::Io)?;
|
|
||||||
if !event::poll(Duration::from_millis(100)).map_err(StandalonePickerError::Io)? {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let TermEvent::Key(key) = event::read().map_err(StandalonePickerError::Io)? else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if key.kind == KeyEventKind::Release {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
|
||||||
match key.code {
|
|
||||||
KeyCode::Up | KeyCode::Char('k') if !ctrl => {
|
|
||||||
selected = selected.saturating_sub(1);
|
|
||||||
}
|
}
|
||||||
KeyCode::Down | KeyCode::Char('j') if !ctrl => {
|
let TermEvent::Key(key) = event::read().map_err(StandalonePickerError::Io)? else {
|
||||||
selected = (selected + 1).min(records.len() - 1);
|
continue;
|
||||||
|
};
|
||||||
|
if key.kind == KeyEventKind::Release {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
let ctrl = key.modifiers.contains(KeyModifiers::CONTROL);
|
||||||
|
match key.code {
|
||||||
|
KeyCode::Up | KeyCode::Char('k') if !ctrl => {
|
||||||
|
selected = selected.saturating_sub(1);
|
||||||
|
}
|
||||||
|
KeyCode::Down | KeyCode::Char('j') if !ctrl => {
|
||||||
|
selected = (selected + 1).min(records.len() - 1);
|
||||||
|
}
|
||||||
|
KeyCode::Enter => return Ok(Some(records[selected].clone())),
|
||||||
|
KeyCode::Esc => return Ok(None),
|
||||||
|
KeyCode::Char('c') if ctrl => return Ok(None),
|
||||||
|
_ => {}
|
||||||
}
|
}
|
||||||
KeyCode::Enter => return Ok(Some(records[selected].clone())),
|
|
||||||
KeyCode::Esc => return Ok(None),
|
|
||||||
KeyCode::Char('c') if ctrl => return Ok(None),
|
|
||||||
_ => {}
|
|
||||||
}
|
}
|
||||||
}
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn draw(frame: &mut ratatui::Frame<'_>, records: &[StandaloneWorkerRecord], selected: usize) {
|
fn draw(frame: &mut ratatui::Frame<'_>, records: &[StandaloneWorkerRecord], selected: usize) {
|
||||||
@@ -149,7 +143,7 @@ pub(crate) enum StandalonePickerError {
|
|||||||
)]
|
)]
|
||||||
NoWorkers { include_all: bool },
|
NoWorkers { include_all: bool },
|
||||||
#[error("standalone Worker picker I/O failed: {0}")]
|
#[error("standalone Worker picker I/O failed: {0}")]
|
||||||
Io(#[source] io::Error),
|
Io(#[from] io::Error),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@@ -1,17 +1,17 @@
|
|||||||
use std::io::{self, Stdout};
|
use std::io;
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
use std::time::Duration;
|
use std::time::Duration;
|
||||||
|
|
||||||
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
use crossterm::event::{self, Event, KeyCode, KeyEvent, KeyEventKind, KeyModifiers};
|
||||||
use manifest::ProfileDiscovery;
|
use manifest::ProfileDiscovery;
|
||||||
use ratatui::Terminal;
|
|
||||||
use ratatui::backend::CrosstermBackend;
|
|
||||||
use ratatui::layout::{Constraint, Direction, Layout};
|
use ratatui::layout::{Constraint, Direction, Layout};
|
||||||
use ratatui::style::{Color, Modifier, Style};
|
use ratatui::style::{Color, Modifier, Style};
|
||||||
use ratatui::text::{Line, Span};
|
use ratatui::text::{Line, Span};
|
||||||
use ratatui::widgets::Paragraph;
|
use ratatui::widgets::Paragraph;
|
||||||
use thiserror::Error;
|
use thiserror::Error;
|
||||||
|
|
||||||
|
use crate::inline_terminal::{InlineTerminal, with_inline_terminal};
|
||||||
|
|
||||||
const VIEWPORT_HEIGHT: u16 = 6;
|
const VIEWPORT_HEIGHT: u16 = 6;
|
||||||
const FALLBACK_WORKER_NAME: &str = "worker";
|
const FALLBACK_WORKER_NAME: &str = "worker";
|
||||||
|
|
||||||
@@ -182,15 +182,16 @@ pub(crate) fn select(
|
|||||||
return Err(StandaloneSpawnError::NoProfiles);
|
return Err(StandaloneSpawnError::NoProfiles);
|
||||||
}
|
}
|
||||||
|
|
||||||
let terminal = open_inline_terminal()?;
|
with_inline_terminal(VIEWPORT_HEIGHT, |terminal| {
|
||||||
run_picker(
|
run_picker(
|
||||||
terminal,
|
terminal,
|
||||||
SpawnForm::new(worker_name, default_worker_name, choices),
|
SpawnForm::new(worker_name, default_worker_name, choices),
|
||||||
)
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn run_picker(
|
fn run_picker(
|
||||||
mut terminal: Terminal<CrosstermBackend<Stdout>>,
|
terminal: &mut InlineTerminal,
|
||||||
mut form: SpawnForm,
|
mut form: SpawnForm,
|
||||||
) -> Result<Option<StandaloneSpawnSelection>, StandaloneSpawnError> {
|
) -> Result<Option<StandaloneSpawnSelection>, StandaloneSpawnError> {
|
||||||
loop {
|
loop {
|
||||||
@@ -222,13 +223,6 @@ fn run_picker(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn open_inline_terminal() -> io::Result<Terminal<CrosstermBackend<Stdout>>> {
|
|
||||||
let options = ratatui::TerminalOptions {
|
|
||||||
viewport: ratatui::Viewport::Inline(VIEWPORT_HEIGHT),
|
|
||||||
};
|
|
||||||
Terminal::with_options(CrosstermBackend::new(io::stdout()), options)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn profile_choices(registry: &manifest::ProfileRegistry) -> Vec<ProfileChoice> {
|
fn profile_choices(registry: &manifest::ProfileRegistry) -> Vec<ProfileChoice> {
|
||||||
registry
|
registry
|
||||||
.entries()
|
.entries()
|
||||||
|
|||||||
Reference in New Issue
Block a user