Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
eda8d3b99f | ||
|
|
829b6403b7 |
1
.gitignore
vendored
1
.gitignore
vendored
@@ -13,3 +13,4 @@ src/target/
|
||||
.DS_Store
|
||||
|
||||
*txt
|
||||
.gitignore
|
||||
|
||||
15
Cargo.lock
generated
15
Cargo.lock
generated
@@ -1204,7 +1204,7 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "ostiary"
|
||||
version = "1.1.5"
|
||||
version = "1.1.7"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"base64",
|
||||
@@ -1223,6 +1223,7 @@ dependencies = [
|
||||
"tokio",
|
||||
"tokio-util",
|
||||
"toml",
|
||||
"unicode-width 0.1.14",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1586,7 +1587,7 @@ dependencies = [
|
||||
"thiserror 2.0.18",
|
||||
"unicode-segmentation",
|
||||
"unicode-truncate",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -1637,7 +1638,7 @@ dependencies = [
|
||||
"strum",
|
||||
"time",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -2431,9 +2432,15 @@ checksum = "16b380a1238663e5f8a691f9039c73e1cdae598a30e9855f541d29b08b53e9a5"
|
||||
dependencies = [
|
||||
"itertools",
|
||||
"unicode-segmentation",
|
||||
"unicode-width",
|
||||
"unicode-width 0.2.2",
|
||||
]
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.1.14"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "7dd6e30e90baa6f72411720665d41d89b9a3d039dc45b8faea1ddd07f617f6af"
|
||||
|
||||
[[package]]
|
||||
name = "unicode-width"
|
||||
version = "0.2.2"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "ostiary"
|
||||
version = "1.1.5"
|
||||
version = "1.1.7"
|
||||
edition = "2024"
|
||||
|
||||
[dependencies]
|
||||
@@ -21,3 +21,4 @@ libc = "0.2"
|
||||
ed25519-dalek = { version = "2", features = ["rand_core"] }
|
||||
rand = "0.8"
|
||||
base64 = "0.22"
|
||||
unicode-width = "0.1"
|
||||
|
||||
@@ -2,6 +2,7 @@ use crate::error::{AppError, Result};
|
||||
use serde_json::Value;
|
||||
#[cfg(unix)]
|
||||
use libc;
|
||||
use std::io::Write;
|
||||
use std::process::Stdio;
|
||||
use tokio::{
|
||||
io::{AsyncBufReadExt, AsyncWriteExt, BufReader},
|
||||
@@ -89,13 +90,20 @@ pub fn spawn(
|
||||
UnboundedSender<String>,
|
||||
tokio::sync::oneshot::Sender<()>, // kill signal
|
||||
)> {
|
||||
let random: u32 = rand::random();
|
||||
let script_path = std::env::temp_dir().join(format!("ostiary_{}_{}.sh", std::process::id(), random));
|
||||
{
|
||||
let mut f = std::fs::File::create(&script_path)?;
|
||||
f.write_all(script.as_bytes())?;
|
||||
}
|
||||
|
||||
let mut child = Command::new("bash")
|
||||
.arg("-c")
|
||||
.arg(script)
|
||||
.arg(&script_path)
|
||||
.stdin(Stdio::piped())
|
||||
.stdout(Stdio::piped())
|
||||
.stderr(Stdio::piped())
|
||||
.spawn()?;
|
||||
.spawn()
|
||||
.inspect_err(|_| { let _ = std::fs::remove_file(&script_path); })?;
|
||||
|
||||
// Capture the PID before moving child into tasks (needed for kill).
|
||||
let pid = child.id();
|
||||
@@ -159,6 +167,7 @@ pub fn spawn(
|
||||
tokio::spawn(async move {
|
||||
let status = child.wait().await;
|
||||
let code = status.map(|s| s.code().unwrap_or(1)).unwrap_or(1);
|
||||
let _ = std::fs::remove_file(&script_path);
|
||||
let _ = finished_tx.send(code);
|
||||
});
|
||||
|
||||
|
||||
15
src/main.rs
15
src/main.rs
@@ -29,6 +29,8 @@ async fn main() -> Result<()> {
|
||||
config::Config::create_with_endpoint(&url, &name)?
|
||||
};
|
||||
|
||||
cleanup_stale_temp_scripts();
|
||||
|
||||
enable_raw_mode()?;
|
||||
let mut stdout = io::stdout();
|
||||
execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
|
||||
@@ -168,6 +170,19 @@ async fn run_exec(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn cleanup_stale_temp_scripts() {
|
||||
let tmp = std::env::temp_dir();
|
||||
if let Ok(entries) = std::fs::read_dir(&tmp) {
|
||||
for entry in entries.flatten() {
|
||||
let name = entry.file_name();
|
||||
let name = name.to_string_lossy();
|
||||
if name.starts_with("ostiary_") && name.ends_with(".sh") {
|
||||
let _ = std::fs::remove_file(entry.path());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Runs before ratatui starts. Prompts for endpoint URL and name on first launch.
|
||||
fn prompt_first_endpoint() -> Result<(String, String)> {
|
||||
use std::io::{BufRead, BufReader, Write};
|
||||
|
||||
23
src/ui.rs
23
src/ui.rs
@@ -3,6 +3,7 @@ use crate::config::Config;
|
||||
use crate::executor;
|
||||
use crate::app::{App, MessageLevel, Popup, UpdatingStatus};
|
||||
use crate::text_input::TextInput;
|
||||
use unicode_width::UnicodeWidthChar;
|
||||
use ratatui::{
|
||||
layout::{Alignment, Constraint, Direction, Layout, Rect},
|
||||
style::{Color, Modifier, Style},
|
||||
@@ -102,8 +103,11 @@ fn render_menu(f: &mut Frame, app: &mut App, area: Rect) {
|
||||
|
||||
let endpoint_title = app.config.endpoints.iter()
|
||||
.find(|ep| ep.url == app.config.active_endpoint)
|
||||
.map(|ep| format!(" {} ", ep.name))
|
||||
.unwrap_or_else(|| " Меню ".to_string());
|
||||
.map(|ep| Line::from(vec![
|
||||
Span::raw(format!(" {} ", ep.name)),
|
||||
Span::styled(" — [↹] Изменить ", Style::default().fg(Color::DarkGray)),
|
||||
]))
|
||||
.unwrap_or_else(|| Line::from(" Меню "));
|
||||
|
||||
let list = List::new(list_items).block(
|
||||
Block::default()
|
||||
@@ -542,10 +546,11 @@ fn render_popup(f: &mut Frame, popup: &mut Popup, config: &Config) {
|
||||
|
||||
// Real terminal cursor in the active field
|
||||
let af = *active_field;
|
||||
let cx = (chunks[af].x + 1 + fields[af].cursor as u16)
|
||||
let cx = (chunks[af].x + 1 + display_cols(&fields[af].buf, fields[af].cursor))
|
||||
.min(chunks[af].x + chunks[af].width.saturating_sub(2));
|
||||
f.set_cursor_position((cx, chunks[af].y + 1));
|
||||
|
||||
|
||||
let (title, hint) = if edit_index.is_some() {
|
||||
(" Редактировать эндпоинт ", " Enter — далее / сохранить ")
|
||||
} else {
|
||||
@@ -655,7 +660,8 @@ fn render_command(
|
||||
),
|
||||
area,
|
||||
);
|
||||
let cursor_x = (area.x + 1 + input.cursor as u16).min(area.x + area.width.saturating_sub(2));
|
||||
let col = if *secret { input.cursor as u16 } else { display_cols(&input.buf, input.cursor) };
|
||||
let cursor_x = (area.x + 1 + col).min(area.x + area.width.saturating_sub(2));
|
||||
f.set_cursor_position((cursor_x, area.y + 1));
|
||||
}
|
||||
|
||||
@@ -814,6 +820,15 @@ fn render_command(
|
||||
}
|
||||
}
|
||||
|
||||
/// Display-column width of `buf` up to char index `cursor`.
|
||||
/// Accounts for wide characters (emoji, CJK) that occupy 2 terminal columns.
|
||||
fn display_cols(buf: &str, cursor: usize) -> u16 {
|
||||
buf.chars()
|
||||
.take(cursor)
|
||||
.map(|c| UnicodeWidthChar::width(c).unwrap_or(0))
|
||||
.sum::<usize>() as u16
|
||||
}
|
||||
|
||||
fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
|
||||
let popup_layout = Layout::default()
|
||||
.direction(Direction::Vertical)
|
||||
|
||||
Reference in New Issue
Block a user