T18 Terminal: a real shell on a pseudo-terminal through alacritty_terminal 0.26, drawn cell by cell in the theme's ANSI colors, with keys, text input, selection, scrollback, links and regex find; replayed output; tabs, splits and toolbar with a shell selector; command blocks and history; AnsiText, LogViewer and ProcessList
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
601a29a374
commit
8374b77906
30 files changed
+3527
-12
No files matched your search
@@ -0,0 +1,163 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use ely_gpui_component::{
|
||||
forms::{Input, TextInput},
|
||||
terminal::{LogLevel, LogLine, LogViewer, Process, ProcessList},
|
||||
theme::{ActiveTheme, Radius},
|
||||
typography::Caption,
|
||||
};
|
||||
use gpui::{App, IntoElement, ParentElement, SharedString, Styled, Window, div, px};
|
||||
|
||||
use crate::ui::{keep, section, set};
|
||||
|
||||
/// Two hundred lines of a service's day, the same each time.
|
||||
fn log() -> Rc<Vec<LogLine>> {
|
||||
let targets = [
|
||||
"ely::shell",
|
||||
"ely::pty",
|
||||
"gpui::window",
|
||||
"ely::store",
|
||||
"net::http",
|
||||
];
|
||||
let messages = [
|
||||
(LogLevel::Debug, "frame drawn in 4.1 ms"),
|
||||
(LogLevel::Info, "window opened at 1280×820"),
|
||||
(LogLevel::Trace, "key down: cmd-k"),
|
||||
(LogLevel::Info, "\x1b[1mzsh\x1b[0m started, pid 4821"),
|
||||
(LogLevel::Warn, "slow frame: 38 ms, over budget"),
|
||||
(LogLevel::Debug, "layout cached: 212 nodes"),
|
||||
(LogLevel::Info, "GET /api/quotes \x1b[32m200\x1b[0m 18 ms"),
|
||||
(
|
||||
LogLevel::Error,
|
||||
"GET /api/orders \x1b[31m503\x1b[0m retry in 2 s",
|
||||
),
|
||||
(LogLevel::Info, "saved workspace, 3 panes"),
|
||||
];
|
||||
Rc::new(
|
||||
(0..200)
|
||||
.map(|ix: usize| {
|
||||
let (level, message) = messages[(ix * 7 + ix / 3) % messages.len()];
|
||||
LogLine {
|
||||
at: format!(
|
||||
"09:{:02}:{:02}.{:03}",
|
||||
ix / 60 % 60,
|
||||
ix % 60,
|
||||
ix * 37 % 1000
|
||||
)
|
||||
.into(),
|
||||
level,
|
||||
target: targets[ix * 3 % targets.len()].into(),
|
||||
message: message.into(),
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
)
|
||||
}
|
||||
|
||||
fn field(
|
||||
key: &'static str,
|
||||
placeholder: &'static str,
|
||||
window: &mut Window,
|
||||
cx: &mut App,
|
||||
) -> gpui::Entity<TextInput> {
|
||||
window.use_keyed_state(key, cx, move |window, cx| {
|
||||
TextInput::new(window, cx).placeholder(placeholder)
|
||||
})
|
||||
}
|
||||
|
||||
pub fn logs(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let lines = keep("terminal-log", log, window, cx);
|
||||
let least = keep("terminal-least", || LogLevel::Debug, window, cx);
|
||||
let follow = keep("terminal-follow", || true, window, cx);
|
||||
let query = field("terminal-log-query", "Filter lines", window, cx);
|
||||
let (now_lines, now_least, now_follow) =
|
||||
(lines.read(cx).clone(), *least.read(cx), *follow.read(cx));
|
||||
let text = query.read(cx).text().to_string();
|
||||
let (pick, keep_up) = (least.clone(), follow.clone());
|
||||
let theme = cx.theme();
|
||||
section(
|
||||
"LogViewer",
|
||||
"A log from a chosen level up, filtered as you type, following new lines until a scroll up lets go. Long logs draw only the lines in view.",
|
||||
cx,
|
||||
)
|
||||
.child(div().w(px(320.)).child(Input::new(&query)))
|
||||
.child(
|
||||
div()
|
||||
.w(px(840.))
|
||||
.h(px(280.))
|
||||
.p_2()
|
||||
.rounded(theme.radius(Radius::Md))
|
||||
.border_1()
|
||||
.border_color(theme.colors.border)
|
||||
.child(
|
||||
LogViewer::new("terminal-log-viewer", now_lines)
|
||||
.least(now_least)
|
||||
.query(text)
|
||||
.follow(now_follow)
|
||||
.on_least(move |level, _, cx| set(&pick, level, cx))
|
||||
.on_follow(move |on, _, cx| set(&keep_up, on, cx)),
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
fn processes() -> Vec<Process> {
|
||||
let process = |pid, name: &str, user: &str, cpu, memory_mb: u64| Process {
|
||||
pid,
|
||||
name: name.to_string().into(),
|
||||
user: user.to_string().into(),
|
||||
cpu,
|
||||
memory: memory_mb * 1024 * 1024,
|
||||
};
|
||||
vec![
|
||||
process(4821, "zsh", "ely", 0.2, 6),
|
||||
process(512, "WindowServer", "_windowserver", 14.6, 412),
|
||||
process(9001, "gallery", "ely", 23.4, 188),
|
||||
process(733, "rust-analyzer", "ely", 41.8, 1_240),
|
||||
process(88, "launchd", "root", 0.1, 12),
|
||||
process(2210, "cargo", "ely", 6.3, 96),
|
||||
process(3302, "Safari", "ely", 8.9, 734),
|
||||
process(3303, "Safari Networking", "ely", 1.4, 88),
|
||||
process(401, "mds_stores", "root", 3.2, 140),
|
||||
process(6120, "codex", "ely", 2.7, 310),
|
||||
]
|
||||
}
|
||||
|
||||
pub fn process_list(window: &mut Window, cx: &mut App) -> impl IntoElement + use<> {
|
||||
let running = keep("terminal-processes", processes, window, cx);
|
||||
let chosen = keep("terminal-chosen", Vec::<u32>::new, window, cx);
|
||||
let said = keep("terminal-ended", || None::<SharedString>, window, cx);
|
||||
let query = field("terminal-process-query", "Find a process", window, cx);
|
||||
let (now, picked, told) = (
|
||||
running.read(cx).clone(),
|
||||
chosen.read(cx).clone(),
|
||||
said.read(cx).clone(),
|
||||
);
|
||||
let text = query.read(cx).text().to_string();
|
||||
let (select, end, clear, tell) = (
|
||||
chosen.clone(),
|
||||
running.clone(),
|
||||
chosen.clone(),
|
||||
said.clone(),
|
||||
);
|
||||
section(
|
||||
"ProcessList",
|
||||
"Running processes, busiest first: found by a query, sorted by any column, and the selected ones ended.",
|
||||
cx,
|
||||
)
|
||||
.child(div().w(px(320.)).child(Input::new(&query)))
|
||||
.child(
|
||||
div().w(px(840.)).child(
|
||||
ProcessList::new("terminal-process-list", now)
|
||||
.query(text)
|
||||
.selected(picked)
|
||||
.on_select(move |pids, _, cx| set(&select, pids, cx))
|
||||
.on_end(move |pids, _, cx| {
|
||||
let left: Vec<Process> = end.read(cx).iter().filter(|process| !pids.contains(&process.pid)).cloned().collect();
|
||||
set(&end, left, cx);
|
||||
set(&clear, Vec::new(), cx);
|
||||
set(&tell, Some(format!("Ended {} process{}.", pids.len(), if pids.len() == 1 { "" } else { "es" }).into()), cx);
|
||||
}),
|
||||
),
|
||||
)
|
||||
.children(told.map(Caption::new))
|
||||
}
|
||||
Reference in new issue
Block a user