Format Rust sources at 100 columns
This commit is contained in:
1 parent
4661f595f8
commit
610478f8c7
336 files changed
+23497
-7892
No files matched your search
+55
-15
@@ -98,7 +98,11 @@ fn value(
|
||||
message: &Option<String>,
|
||||
remaining_ms: Option<u64>,
|
||||
) -> Result<Option<Value>, Box<dyn Error>> {
|
||||
if usize::from(progress.is_some()) + usize::from(message.is_some()) + usize::from(remaining_ms.is_some()) > 1 {
|
||||
if usize::from(progress.is_some())
|
||||
+ usize::from(message.is_some())
|
||||
+ usize::from(remaining_ms.is_some())
|
||||
> 1
|
||||
{
|
||||
return Err("choose one activity value".into());
|
||||
}
|
||||
Ok(progress
|
||||
@@ -111,7 +115,11 @@ fn value(
|
||||
})
|
||||
}))
|
||||
}
|
||||
fn publish(socket: &Path, owner: OwnerScope, publish: Publish) -> Result<serde_json::Value, Box<dyn Error>> {
|
||||
fn publish(
|
||||
socket: &Path,
|
||||
owner: OwnerScope,
|
||||
publish: Publish,
|
||||
) -> Result<serde_json::Value, Box<dyn Error>> {
|
||||
publish_with_timeout(socket, owner, publish, std::time::Duration::from_secs(2))
|
||||
}
|
||||
fn publish_with_timeout(
|
||||
@@ -207,18 +215,28 @@ pub fn command(action: &Action, socket: &Path, json_output: bool) -> Result<(),
|
||||
},
|
||||
message: message.clone(),
|
||||
},
|
||||
Action::Cancel { local, handle: token } => Publish::Cancel {
|
||||
Action::Cancel {
|
||||
local,
|
||||
handle: token,
|
||||
} => Publish::Cancel {
|
||||
local: local.clone(),
|
||||
handle: handle(token)?,
|
||||
},
|
||||
Action::Hook { local, command, cwd } => Publish::Hook {
|
||||
Action::Hook {
|
||||
local,
|
||||
command,
|
||||
cwd,
|
||||
} => Publish::Hook {
|
||||
local: local.clone(),
|
||||
command: command.clone(),
|
||||
cwd: cwd.clone(),
|
||||
},
|
||||
Action::List => {
|
||||
let mut stream = std::os::unix::net::UnixStream::connect(socket)?;
|
||||
return super::output::print(&super::request(&mut stream, "activity-get", json!({}))?, json_output);
|
||||
return super::output::print(
|
||||
&super::request(&mut stream, "activity-get", json!({}))?,
|
||||
json_output,
|
||||
);
|
||||
}
|
||||
};
|
||||
let result = publish(socket, OwnerScope::Parent, request)?;
|
||||
@@ -275,9 +293,12 @@ pub fn run(
|
||||
}
|
||||
let adapter = (configured_adapter || cargo_json)
|
||||
&& program == "cargo"
|
||||
&& args
|
||||
.get(1)
|
||||
.is_some_and(|operation| matches!(operation.as_str(), "build" | "check" | "test" | "run" | "clippy"));
|
||||
&& args.get(1).is_some_and(|operation| {
|
||||
matches!(
|
||||
operation.as_str(),
|
||||
"build" | "check" | "test" | "run" | "clippy"
|
||||
)
|
||||
});
|
||||
let mut command = Command::new(program);
|
||||
command.args(&args[1..]);
|
||||
if adapter && !args.iter().any(|arg| arg.starts_with("--message-format")) {
|
||||
@@ -296,7 +317,11 @@ pub fn run(
|
||||
command
|
||||
.stdin(Stdio::inherit())
|
||||
.stderr(Stdio::inherit())
|
||||
.stdout(if adapter { Stdio::piped() } else { Stdio::inherit() });
|
||||
.stdout(if adapter {
|
||||
Stdio::piped()
|
||||
} else {
|
||||
Stdio::inherit()
|
||||
});
|
||||
// Terminal-generated signals reach both processes in the shell's job group.
|
||||
// The wrapper waits for the actual child result; the child resets inherited
|
||||
// dispositions before exec. SIGTSTP/SIGCONT retain normal job control.
|
||||
@@ -336,10 +361,15 @@ pub fn run(
|
||||
let drain_deadline = output::Deadline::default();
|
||||
let reader = child.stdout.take().map(|stdout| {
|
||||
let deadline = drain_deadline.clone();
|
||||
let updates = publisher.as_ref().map(|publisher| publisher.updates.clone());
|
||||
let updates = publisher
|
||||
.as_ref()
|
||||
.map(|publisher| publisher.updates.clone());
|
||||
let (done, finished) = std::sync::mpsc::sync_channel(1);
|
||||
std::thread::spawn(move || {
|
||||
let mut input = io::BufReader::new(output::Output { pipe: stdout, deadline });
|
||||
let mut input = io::BufReader::new(output::Output {
|
||||
pipe: stdout,
|
||||
deadline,
|
||||
});
|
||||
let mut stdout = io::stdout().lock();
|
||||
let mut units = 0_u64;
|
||||
let mut last = std::time::Instant::now();
|
||||
@@ -362,7 +392,9 @@ pub fn run(
|
||||
}
|
||||
} else if message["reason"] == "compiler-artifact" {
|
||||
units = units.saturating_add(1);
|
||||
} else if message["reason"] != "build-finished" && message["reason"] != "build-script-executed" {
|
||||
} else if message["reason"] != "build-finished"
|
||||
&& message["reason"] != "build-script-executed"
|
||||
{
|
||||
let _ = stdout.write_all(&bytes);
|
||||
}
|
||||
if last.elapsed() >= std::time::Duration::from_millis(200) {
|
||||
@@ -373,7 +405,11 @@ pub fn run(
|
||||
}
|
||||
continue;
|
||||
}
|
||||
if stdout.write_all(&bytes).and_then(|_| stdout.flush()).is_err() {
|
||||
if stdout
|
||||
.write_all(&bytes)
|
||||
.and_then(|_| stdout.flush())
|
||||
.is_err()
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
@@ -384,14 +420,18 @@ pub fn run(
|
||||
});
|
||||
let status = child.wait()?;
|
||||
CHILD.store(0, std::sync::atomic::Ordering::Relaxed);
|
||||
*drain_deadline.lock().unwrap() = Some(std::time::Instant::now() + std::time::Duration::from_millis(250));
|
||||
*drain_deadline.lock().unwrap() =
|
||||
Some(std::time::Instant::now() + std::time::Duration::from_millis(250));
|
||||
if let Some(reader) = reader {
|
||||
// Descendants may inherit the pipe after the direct child exits. Drain
|
||||
// buffered output briefly, but never wait for those descendants' EOF.
|
||||
let _ = reader.recv_timeout(std::time::Duration::from_millis(350));
|
||||
}
|
||||
if let Some(publisher) = publisher {
|
||||
let result = if matches!(status.signal(), Some(libc::SIGINT | libc::SIGTERM | libc::SIGQUIT)) {
|
||||
let result = if matches!(
|
||||
status.signal(),
|
||||
Some(libc::SIGINT | libc::SIGTERM | libc::SIGQUIT)
|
||||
) {
|
||||
State::Cancelled
|
||||
} else if status.success() {
|
||||
State::Done
|
||||
|
||||
@@ -102,23 +102,34 @@ mod tests {
|
||||
let (release, released) = mpsc::channel();
|
||||
let server = std::thread::spawn(move || {
|
||||
let (mut first, _) = listener.accept().unwrap();
|
||||
first.set_read_timeout(Some(Duration::from_secs(1))).unwrap();
|
||||
first
|
||||
.set_read_timeout(Some(Duration::from_secs(1)))
|
||||
.unwrap();
|
||||
let request: Request = read_frame(&mut first).unwrap();
|
||||
seen.send(()).unwrap();
|
||||
released.recv_timeout(Duration::from_secs(1)).unwrap();
|
||||
// The progress timeout can already have elapsed on a loaded host.
|
||||
let _ = write_frame(&mut first, &Response::success(request.id, serde_json::json!({})));
|
||||
let _ = write_frame(
|
||||
&mut first,
|
||||
&Response::success(request.id, serde_json::json!({})),
|
||||
);
|
||||
let (mut latest, _) = listener.accept().unwrap();
|
||||
let request: Request = read_frame(&mut latest).unwrap();
|
||||
let wire: ferese_ipc::activities::Request = serde_json::from_value(request.args).unwrap();
|
||||
let wire: ferese_ipc::activities::Request =
|
||||
serde_json::from_value(request.args).unwrap();
|
||||
assert!(
|
||||
matches!(wire.publish, Publish::Update { value: Some(Value::Text(ref text)), .. } if text == "1000 units compiled")
|
||||
);
|
||||
write_frame(&mut latest, &Response::success(request.id, serde_json::json!({}))).unwrap();
|
||||
write_frame(
|
||||
&mut latest,
|
||||
&Response::success(request.id, serde_json::json!({})),
|
||||
)
|
||||
.unwrap();
|
||||
seen.send(()).unwrap();
|
||||
let (mut end, _) = listener.accept().unwrap();
|
||||
let request: Request = read_frame(&mut end).unwrap();
|
||||
let wire: ferese_ipc::activities::Request = serde_json::from_value(request.args).unwrap();
|
||||
let wire: ferese_ipc::activities::Request =
|
||||
serde_json::from_value(request.args).unwrap();
|
||||
assert!(matches!(
|
||||
wire.publish,
|
||||
Publish::End {
|
||||
@@ -126,7 +137,11 @@ mod tests {
|
||||
..
|
||||
}
|
||||
));
|
||||
write_frame(&mut end, &Response::success(request.id, serde_json::json!({}))).unwrap();
|
||||
write_frame(
|
||||
&mut end,
|
||||
&Response::success(request.id, serde_json::json!({})),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
let publisher = Publisher::new(
|
||||
socket,
|
||||
|
||||
@@ -8,7 +8,10 @@ fn prepare(source: &str) -> Result<String, Box<dyn Error>> {
|
||||
return Ok(source.into());
|
||||
}
|
||||
if doc.get("panels").is_none() {
|
||||
doc.set("panels", serde_json::to_value(vec![panel::Panel::default()])?)?;
|
||||
doc.set(
|
||||
"panels",
|
||||
serde_json::to_value(vec![panel::Panel::default()])?,
|
||||
)?;
|
||||
}
|
||||
let panels: Vec<panel::Panel> = serde_json::from_value(doc.get("panels").unwrap().clone())?;
|
||||
panel::validate(&panels)?;
|
||||
@@ -25,7 +28,11 @@ fn prepare(source: &str) -> Result<String, Box<dyn Error>> {
|
||||
continue;
|
||||
}
|
||||
let mut id = "activities".to_string();
|
||||
while groups.iter().flat_map(|group| &group.items).any(|item| item.id.0 == id) {
|
||||
while groups
|
||||
.iter()
|
||||
.flat_map(|group| &group.items)
|
||||
.any(|item| item.id.0 == id)
|
||||
{
|
||||
id.push('-');
|
||||
}
|
||||
let item = serde_json::to_value(panel::Item::new(&id, panel::ItemKind::Activities))?;
|
||||
@@ -43,7 +50,10 @@ fn prepare(source: &str) -> Result<String, Box<dyn Error>> {
|
||||
} else {
|
||||
doc.add(
|
||||
&format!("panels.{index}.end.groups"),
|
||||
vec![("id".into(), json!("activities")), ("items".into(), json!([item]))],
|
||||
vec![
|
||||
("id".into(), json!("activities")),
|
||||
("items".into(), json!([item])),
|
||||
],
|
||||
)?;
|
||||
}
|
||||
}
|
||||
@@ -64,7 +74,10 @@ pub fn configure(path: &Path) -> Result<(), Box<dyn Error>> {
|
||||
ferese_ipc::activities::now_ms()
|
||||
));
|
||||
file.replace(&desired, Some(&backup), |_| Ok(()))?;
|
||||
println!("Configured Live Activities; previous config: {}", backup.display());
|
||||
println!(
|
||||
"Configured Live Activities; previous config: {}",
|
||||
backup.display()
|
||||
);
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -73,20 +86,29 @@ mod tests {
|
||||
use super::*;
|
||||
#[test]
|
||||
fn setup_preserves_layout_comments_rules_and_explicit_disable() {
|
||||
let source =
|
||||
"// keep my layout\npanel main { style morph; end { group tools { item activities kind=clock; }; }; }\n";
|
||||
let source = "// keep my layout\npanel main { style morph; end { group tools { item activities kind=clock; }; }; }\n";
|
||||
let next = prepare(source).unwrap();
|
||||
assert!(next.contains("// keep my layout"));
|
||||
let doc = Document::parse(&next).unwrap();
|
||||
assert_eq!(doc.get("panels.0.style"), Some(&json!("morph")));
|
||||
assert_eq!(doc.get("panels.0.end.groups.0.items.0.kind"), Some(&json!("clock")));
|
||||
assert_eq!(doc.get("panels.0.end.groups.0.items.1.id"), Some(&json!("activities-")));
|
||||
assert_eq!(
|
||||
doc.get("panels.0.end.groups.0.items.0.kind"),
|
||||
Some(&json!("clock"))
|
||||
);
|
||||
assert_eq!(
|
||||
doc.get("panels.0.end.groups.0.items.1.id"),
|
||||
Some(&json!("activities-"))
|
||||
);
|
||||
assert_eq!(prepare(&next).unwrap(), next);
|
||||
let disabled = "activities { sources { shell-hook #false; }; }\n";
|
||||
assert_eq!(prepare(disabled).unwrap(), disabled);
|
||||
let hidden = "panel main { end { group tools { item tasks kind=activities visible=#false; }; }; }\n";
|
||||
let hidden =
|
||||
"panel main { end { group tools { item tasks kind=activities visible=#false; }; }; }\n";
|
||||
let hidden = Document::parse(&prepare(hidden).unwrap()).unwrap();
|
||||
assert_eq!(hidden.get("panels.0.end.groups.0.items.0.visible"), Some(&json!(false)));
|
||||
assert_eq!(
|
||||
hidden.get("panels.0.end.groups.0.items.0.visible"),
|
||||
Some(&json!(false))
|
||||
);
|
||||
assert_eq!(
|
||||
hidden
|
||||
.get("panels.0.end.groups.0.items")
|
||||
@@ -97,9 +119,13 @@ mod tests {
|
||||
1
|
||||
);
|
||||
assert!(prepare("").is_ok());
|
||||
let partial = Document::parse(&prepare("activities { max-visible 1; }\n").unwrap()).unwrap();
|
||||
let partial =
|
||||
Document::parse(&prepare("activities { max-visible 1; }\n").unwrap()).unwrap();
|
||||
assert_eq!(partial.get("activities.max_visible"), Some(&json!(1)));
|
||||
assert_eq!(partial.get("activities.sources.shell_hook"), Some(&json!(true)));
|
||||
assert_eq!(
|
||||
partial.get("activities.sources.shell_hook"),
|
||||
Some(&json!(true))
|
||||
);
|
||||
assert!(prepare("activities { unknown 1; }").is_err());
|
||||
}
|
||||
|
||||
@@ -113,12 +139,22 @@ mod tests {
|
||||
let backups: Vec<_> = std::fs::read_dir(directory.path())
|
||||
.unwrap()
|
||||
.flatten()
|
||||
.filter(|file| file.file_name().to_string_lossy().contains(".before-activities-"))
|
||||
.filter(|file| {
|
||||
file.file_name()
|
||||
.to_string_lossy()
|
||||
.contains(".before-activities-")
|
||||
})
|
||||
.collect();
|
||||
assert_eq!(backups.len(), 1);
|
||||
assert_eq!(std::fs::read_to_string(backups[0].path()).unwrap(), "// original\n");
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(backups[0].path()).unwrap(),
|
||||
"// original\n"
|
||||
);
|
||||
std::fs::write(&path, "activities { bad #true; }\n").unwrap();
|
||||
assert!(configure(&path).is_err());
|
||||
assert_eq!(std::fs::read_to_string(&path).unwrap(), "activities { bad #true; }\n");
|
||||
assert_eq!(
|
||||
std::fs::read_to_string(&path).unwrap(),
|
||||
"activities { bad #true; }\n"
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -62,7 +62,12 @@ impl Stream {
|
||||
self.deadline
|
||||
.checked_duration_since(Instant::now())
|
||||
.filter(|remaining| !remaining.is_zero())
|
||||
.ok_or_else(|| io::Error::new(io::ErrorKind::TimedOut, "activity publication deadline expired"))
|
||||
.ok_or_else(|| {
|
||||
io::Error::new(
|
||||
io::ErrorKind::TimedOut,
|
||||
"activity publication deadline expired",
|
||||
)
|
||||
})
|
||||
}
|
||||
}
|
||||
impl Read for Stream {
|
||||
@@ -107,7 +112,10 @@ mod tests {
|
||||
});
|
||||
{
|
||||
let mut stream = connect(&path, Duration::from_millis(100)).unwrap();
|
||||
assert!(super::super::super::request(&mut stream, "activity-get", serde_json::json!({})).is_err());
|
||||
assert!(
|
||||
super::super::super::request(&mut stream, "activity-get", serde_json::json!({}))
|
||||
.is_err()
|
||||
);
|
||||
}
|
||||
closed
|
||||
.recv_timeout(Duration::from_millis(400))
|
||||
|
||||
@@ -33,14 +33,20 @@ pub(crate) fn run() -> Result<(), Box<dyn std::error::Error>> {
|
||||
if system_directories.is_empty() {
|
||||
system_directories.push(PathBuf::from("/etc/xdg"));
|
||||
}
|
||||
let directories = std::iter::once(home).chain(system_directories).collect::<Vec<_>>();
|
||||
let directories = std::iter::once(home)
|
||||
.chain(system_directories)
|
||||
.collect::<Vec<_>>();
|
||||
let mut entries = BTreeMap::new();
|
||||
for directory in directories {
|
||||
let Ok(files) = fs::read_dir(directory.join("autostart")) else {
|
||||
continue;
|
||||
};
|
||||
for file in files.flatten() {
|
||||
if file.path().extension().is_some_and(|extension| extension == "desktop") {
|
||||
if file
|
||||
.path()
|
||||
.extension()
|
||||
.is_some_and(|extension| extension == "desktop")
|
||||
{
|
||||
entries.entry(file.file_name()).or_insert(file.path());
|
||||
}
|
||||
}
|
||||
@@ -100,7 +106,9 @@ fn read_entry(path: &Path) -> Option<BTreeMap<String, String>> {
|
||||
return None;
|
||||
}
|
||||
let mut source = String::new();
|
||||
file.take(1024 * 1024 + 1).read_to_string(&mut source).ok()?;
|
||||
file.take(1024 * 1024 + 1)
|
||||
.read_to_string(&mut source)
|
||||
.ok()?;
|
||||
if source.len() > 1024 * 1024 {
|
||||
return None;
|
||||
}
|
||||
@@ -142,12 +150,15 @@ fn enabled(entry: &BTreeMap<String, String>, desktop: &str) -> bool {
|
||||
fn available(program: &str) -> bool {
|
||||
use std::os::unix::fs::PermissionsExt;
|
||||
let executable = |path: &Path| {
|
||||
fs::metadata(path).is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
|
||||
fs::metadata(path)
|
||||
.is_ok_and(|metadata| metadata.is_file() && metadata.permissions().mode() & 0o111 != 0)
|
||||
};
|
||||
if program.contains('/') {
|
||||
executable(Path::new(program))
|
||||
} else {
|
||||
env::var_os("PATH").is_some_and(|paths| env::split_paths(&paths).any(|path| executable(&path.join(program))))
|
||||
env::var_os("PATH").is_some_and(|paths| {
|
||||
env::split_paths(&paths).any(|path| executable(&path.join(program)))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+19
-6
@@ -273,7 +273,9 @@ impl Command {
|
||||
Self::CycleColumnWidth => ("cycle-column-width", json!({})),
|
||||
Self::CenterColumn => ("center-column", json!({})),
|
||||
Self::ToggleColumnTabbed => ("toggle-column-tabbed", json!({})),
|
||||
Self::JoinTab { direction } => ("join-tab", json!({"direction": value_name(direction)})),
|
||||
Self::JoinTab { direction } => {
|
||||
("join-tab", json!({"direction": value_name(direction)}))
|
||||
}
|
||||
Self::LeaveTab => ("leave-tab", json!({})),
|
||||
Self::NextTab => ("next-tab", json!({})),
|
||||
Self::PreviousTab => ("previous-tab", json!({})),
|
||||
@@ -297,9 +299,14 @@ impl Command {
|
||||
Self::Resize { direction } => ("resize", json!({"direction": value_name(direction)})),
|
||||
Self::Workspace { index } => ("workspace", json!({"index": index})),
|
||||
Self::MoveToWorkspace { index } => ("move-to-workspace", json!({"index": index})),
|
||||
Self::OutputLayout { layout } => ("output-layout", json!({"layout": value_name(layout)})),
|
||||
Self::OutputLayout { layout } => {
|
||||
("output-layout", json!({"layout": value_name(layout)}))
|
||||
}
|
||||
Self::OutputProfile { name } => ("output-profile", json!({"name": name})),
|
||||
Self::OutputInternal { state } => ("output-internal", json!({"enabled": matches!(state, Switch::On)})),
|
||||
Self::OutputInternal { state } => (
|
||||
"output-internal",
|
||||
json!({"enabled": matches!(state, Switch::On)}),
|
||||
),
|
||||
Self::Screenshot {
|
||||
geometry: Some(geometry),
|
||||
} => ("screenshot", json!({"geometry": geometry})),
|
||||
@@ -315,11 +322,17 @@ impl Command {
|
||||
Self::Media {
|
||||
action: None | Some(MediaAction::Get),
|
||||
} => ("media-get", json!({})),
|
||||
Self::Media { action: Some(action) } => {
|
||||
Self::Media {
|
||||
action: Some(action),
|
||||
} => {
|
||||
let args = match action {
|
||||
MediaAction::Pin { player } => json!({"action": "pin", "player": player}),
|
||||
MediaAction::Ignore { player } => json!({"action": "ignore", "player": player, "ignored": true}),
|
||||
MediaAction::Unignore { player } => json!({"action": "ignore", "player": player, "ignored": false}),
|
||||
MediaAction::Ignore { player } => {
|
||||
json!({"action": "ignore", "player": player, "ignored": true})
|
||||
}
|
||||
MediaAction::Unignore { player } => {
|
||||
json!({"action": "ignore", "player": player, "ignored": false})
|
||||
}
|
||||
MediaAction::PlayPause => json!({"action": "play-pause"}),
|
||||
MediaAction::Next => json!({"action": "next"}),
|
||||
MediaAction::Previous => json!({"action": "previous"}),
|
||||
|
||||
+68
-19
@@ -71,7 +71,11 @@ fn main() -> Result<(), Box<dyn Error>> {
|
||||
}
|
||||
}
|
||||
|
||||
fn request(stream: &mut (impl io::Read + io::Write), command: &str, args: Value) -> Result<Value, Box<dyn Error>> {
|
||||
fn request(
|
||||
stream: &mut (impl io::Read + io::Write),
|
||||
command: &str,
|
||||
args: Value,
|
||||
) -> Result<Value, Box<dyn Error>> {
|
||||
write_frame(
|
||||
stream,
|
||||
&Request {
|
||||
@@ -92,10 +96,16 @@ fn request(stream: &mut (impl io::Read + io::Write), command: &str, args: Value)
|
||||
Ok(response.result.unwrap_or(Value::Null))
|
||||
}
|
||||
|
||||
fn theme_command(action: &ThemeAction, socket: &Path, json_output: bool) -> Result<(), Box<dyn Error>> {
|
||||
fn theme_command(
|
||||
action: &ThemeAction,
|
||||
socket: &Path,
|
||||
json_output: bool,
|
||||
) -> Result<(), Box<dyn Error>> {
|
||||
let mut connection = ferese_ipc::Connection::connect_to(socket)?;
|
||||
let result = match action {
|
||||
ThemeAction::Get => serde_json::to_value(connection.get(ferese_config::families::builtins)?)?,
|
||||
ThemeAction::Get => {
|
||||
serde_json::to_value(connection.get(ferese_config::families::builtins)?)?
|
||||
}
|
||||
ThemeAction::Subscribe => {
|
||||
let mut snapshot = connection.get(ferese_config::families::builtins)?;
|
||||
let mut stdout = io::stdout().lock();
|
||||
@@ -103,7 +113,8 @@ fn theme_command(action: &ThemeAction, socket: &Path, json_output: bool) -> Resu
|
||||
if !output::write_json_line(&mut stdout, &snapshot)? {
|
||||
return Ok(());
|
||||
}
|
||||
snapshot = connection.watch(snapshot.revision, ferese_config::families::builtins)?;
|
||||
snapshot =
|
||||
connection.watch(snapshot.revision, ferese_config::families::builtins)?;
|
||||
}
|
||||
}
|
||||
ThemeAction::Mode { mode } => connection.call(
|
||||
@@ -139,7 +150,8 @@ fn event_stream(socket: &Path) -> Result<(), Box<dyn Error>> {
|
||||
|
||||
#[cfg(test)]
|
||||
fn parse_args(args: impl IntoIterator<Item = String>) -> Result<(String, Value), String> {
|
||||
let cli = Cli::try_parse_from(std::iter::once("feresectl".to_owned()).chain(args)).map_err(|e| e.to_string())?;
|
||||
let cli = Cli::try_parse_from(std::iter::once("feresectl".to_owned()).chain(args))
|
||||
.map_err(|e| e.to_string())?;
|
||||
cli.command
|
||||
.ipc()
|
||||
.map(|(command, args)| (command.into(), args))
|
||||
@@ -224,7 +236,8 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn a_failed_x11_retry_surfaces_the_compositor_error_code() {
|
||||
let response = Response::error(1, "x11_unavailable", "X11 is disabled in the configuration");
|
||||
let response =
|
||||
Response::error(1, "x11_unavailable", "X11 is disabled in the configuration");
|
||||
let mut bytes = Vec::new();
|
||||
write_frame(&mut bytes, &response).expect("encode");
|
||||
let decoded: Response = read_frame(&mut bytes.as_slice()).expect("decode");
|
||||
@@ -258,7 +271,9 @@ mod tests {
|
||||
fn display_mode_commands_validate_layouts() {
|
||||
for layout in ["internal-only", "external-only", "extend", "mirror"] {
|
||||
assert_eq!(
|
||||
parse_args(["output-layout".into(), layout.into()]).unwrap().1,
|
||||
parse_args(["output-layout".into(), layout.into()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({"layout":layout})
|
||||
);
|
||||
}
|
||||
@@ -287,11 +302,15 @@ mod tests {
|
||||
#[test]
|
||||
fn parses_direction_and_workspace_commands() {
|
||||
assert_eq!(
|
||||
parse_args(["focus".to_owned(), "left".to_owned()]).unwrap().1,
|
||||
parse_args(["focus".to_owned(), "left".to_owned()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({ "direction": "left" })
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(["workspace".to_owned(), "7".to_owned()]).unwrap().1,
|
||||
parse_args(["workspace".to_owned(), "7".to_owned()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({ "index": 7 })
|
||||
);
|
||||
assert_eq!(
|
||||
@@ -304,7 +323,10 @@ mod tests {
|
||||
);
|
||||
assert!(parse_args(["workspace-back-and-forth".into(), "2".into()]).is_err());
|
||||
for command in ["focus-last-window", "focus-mru-next", "focus-mru-previous"] {
|
||||
assert_eq!(parse_args([command.into()]).unwrap(), (command.into(), json!({})));
|
||||
assert_eq!(
|
||||
parse_args([command.into()]).unwrap(),
|
||||
(command.into(), json!({}))
|
||||
);
|
||||
assert!(parse_args([command.into(), "2".into()]).is_err());
|
||||
}
|
||||
}
|
||||
@@ -343,24 +365,40 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn output_commands_have_consistent_arguments() {
|
||||
for command in ["outputs", "output-profiles", "output-confirm", "output-revert"] {
|
||||
assert_eq!(parse_args([command.into()]).unwrap(), (command.into(), json!({})));
|
||||
for command in [
|
||||
"outputs",
|
||||
"output-profiles",
|
||||
"output-confirm",
|
||||
"output-revert",
|
||||
] {
|
||||
assert_eq!(
|
||||
parse_args([command.into()]).unwrap(),
|
||||
(command.into(), json!({}))
|
||||
);
|
||||
assert!(parse_args([command.into(), "extra".into()]).is_err());
|
||||
}
|
||||
assert_eq!(
|
||||
parse_args(["output-profile".into(), "auto".into()]).unwrap().1,
|
||||
parse_args(["output-profile".into(), "auto".into()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({"name": "auto"})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(["output-profile".into(), "desk".into()]).unwrap().1,
|
||||
parse_args(["output-profile".into(), "desk".into()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({"name": "desk"})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(["output-internal".into(), "off".into()]).unwrap().1,
|
||||
parse_args(["output-internal".into(), "off".into()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({"enabled": false})
|
||||
);
|
||||
assert_eq!(
|
||||
parse_args(["output-internal".into(), "on".into()]).unwrap().1,
|
||||
parse_args(["output-internal".into(), "on".into()])
|
||||
.unwrap()
|
||||
.1,
|
||||
json!({"enabled": true})
|
||||
);
|
||||
assert!(parse_args(["output-internal".into(), "maybe".into()]).is_err());
|
||||
@@ -377,15 +415,26 @@ mod tests {
|
||||
}
|
||||
assert!(parse_args(["join-tab".into()]).is_err());
|
||||
assert!(parse_args(["join-tab".into(), "diagonal".into()]).is_err());
|
||||
for action in ["toggle-column-tabbed", "leave-tab", "next-tab", "previous-tab"] {
|
||||
assert_eq!(parse_args([action.into()]).unwrap(), (action.into(), json!({})));
|
||||
for action in [
|
||||
"toggle-column-tabbed",
|
||||
"leave-tab",
|
||||
"next-tab",
|
||||
"previous-tab",
|
||||
] {
|
||||
assert_eq!(
|
||||
parse_args([action.into()]).unwrap(),
|
||||
(action.into(), json!({}))
|
||||
);
|
||||
assert!(parse_args([action.into(), "extra".into()]).is_err());
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn rejects_unknown_or_missing_arguments() {
|
||||
assert_eq!(parse_args(["exit".to_owned()]).unwrap(), ("exit".to_owned(), json!({})));
|
||||
assert_eq!(
|
||||
parse_args(["exit".to_owned()]).unwrap(),
|
||||
("exit".to_owned(), json!({}))
|
||||
);
|
||||
assert!(parse_args(["exit".to_owned(), "extra".to_owned()]).is_err());
|
||||
assert!(parse_args(["focus".to_owned()]).is_err());
|
||||
assert!(parse_args(["reload-config".to_owned()]).is_ok());
|
||||
|
||||
@@ -63,7 +63,10 @@ fn terminal_text(text: &str) -> String {
|
||||
}
|
||||
|
||||
/// Return false when a downstream consumer closes stdout.
|
||||
pub fn write_json_line(writer: &mut impl Write, value: &impl serde::Serialize) -> Result<bool, Box<dyn Error>> {
|
||||
pub fn write_json_line(
|
||||
writer: &mut impl Write,
|
||||
value: &impl serde::Serialize,
|
||||
) -> Result<bool, Box<dyn Error>> {
|
||||
let line = serde_json::to_vec(value)?;
|
||||
match writer
|
||||
.write_all(&line)
|
||||
|
||||
+78
-20
@@ -29,7 +29,11 @@ fn serve(path: &Path, expected: &'static str, value: serde_json::Value) -> threa
|
||||
}
|
||||
|
||||
fn success(output: Output) -> String {
|
||||
assert!(output.status.success(), "{}", String::from_utf8_lossy(&output.stderr));
|
||||
assert!(
|
||||
output.status.success(),
|
||||
"{}",
|
||||
String::from_utf8_lossy(&output.stderr)
|
||||
);
|
||||
String::from_utf8(output.stdout).unwrap()
|
||||
}
|
||||
|
||||
@@ -88,7 +92,14 @@ fn explicit_socket_targets_the_nested_instance_for_queries_and_theme_commands()
|
||||
host_listener.set_nonblocking(true).unwrap();
|
||||
for args in [
|
||||
vec!["--socket", nested.to_str().unwrap(), "-j", "windows"],
|
||||
vec!["theme", "mode", "dark", "--socket", nested.to_str().unwrap(), "--json"],
|
||||
vec![
|
||||
"theme",
|
||||
"mode",
|
||||
"dark",
|
||||
"--socket",
|
||||
nested.to_str().unwrap(),
|
||||
"--json",
|
||||
],
|
||||
] {
|
||||
let worker = serve(
|
||||
&nested,
|
||||
@@ -119,7 +130,11 @@ fn socket_environment_and_runtime_fallback_use_the_same_query_path() {
|
||||
std::fs::create_dir(directory.path().join("ferese")).unwrap();
|
||||
let socket = directory.path().join("ferese/control.sock");
|
||||
for environment in ["FERESE_SOCKET", "XDG_RUNTIME_DIR"] {
|
||||
let worker = serve(&socket, "outputs", json!([{"name": "nested", "enabled": true}]));
|
||||
let worker = serve(
|
||||
&socket,
|
||||
"outputs",
|
||||
json!([{"name": "nested", "enabled": true}]),
|
||||
);
|
||||
let target = if environment == "FERESE_SOCKET" {
|
||||
socket.as_path()
|
||||
} else {
|
||||
@@ -172,9 +187,15 @@ fn event_stream_uses_the_selected_socket_and_prints_complete_json_lines() {
|
||||
let request: Request = read_frame(&mut stream).unwrap();
|
||||
assert_eq!(request.command, "event-stream");
|
||||
assert_eq!(request.args["version"], VERSION);
|
||||
write_frame(&mut stream, &Response::success(request.id, json!({"version": VERSION}))).unwrap();
|
||||
write_frame(
|
||||
&mut stream,
|
||||
&Response::success(request.id, json!({"version": VERSION})),
|
||||
)
|
||||
.unwrap();
|
||||
write_frame(&mut stream, &event).unwrap();
|
||||
stopped.recv_timeout(std::time::Duration::from_secs(5)).unwrap();
|
||||
stopped
|
||||
.recv_timeout(std::time::Duration::from_secs(5))
|
||||
.unwrap();
|
||||
});
|
||||
let mut child = ctl(&["event-stream", "--socket", socket.to_str().unwrap()])
|
||||
.stdout(Stdio::piped())
|
||||
@@ -202,7 +223,10 @@ fn activity_start_uses_versioned_framing_and_parent_scope() {
|
||||
assert_eq!(request.version, ferese_ipc::VERSION);
|
||||
assert_eq!(request.command, "activity-publish");
|
||||
let wire: ferese_ipc::activities::Request = serde_json::from_value(request.args).unwrap();
|
||||
assert!(matches!(wire.owner, ferese_ipc::activities::OwnerScope::Parent));
|
||||
assert!(matches!(
|
||||
wire.owner,
|
||||
ferese_ipc::activities::OwnerScope::Parent
|
||||
));
|
||||
match wire.publish {
|
||||
ferese_ipc::activities::Publish::Start { local, data, .. } => {
|
||||
assert_eq!(local, "build");
|
||||
@@ -255,7 +279,10 @@ fn run_preserves_arguments_cwd_environment_output_and_exit_when_publishing_fails
|
||||
assert_eq!(output.status.code(), Some(17));
|
||||
assert_eq!(
|
||||
String::from_utf8(output.stdout).unwrap(),
|
||||
format!("argument with spaces;$literal|preserved|{}", dir.path().display())
|
||||
format!(
|
||||
"argument with spaces;$literal|preserved|{}",
|
||||
dir.path().display()
|
||||
)
|
||||
);
|
||||
assert_eq!(output.stderr, b"stderr");
|
||||
}
|
||||
@@ -273,7 +300,9 @@ fn run_preserves_child_exit_and_signal_status() {
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(output.status.code(), Some(19));
|
||||
let output = ctl(&["run", "--", "sh", "-c", "kill -TERM $$"]).output().unwrap();
|
||||
let output = ctl(&["run", "--", "sh", "-c", "kill -TERM $$"])
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(output.status.signal(), Some(libc::SIGTERM));
|
||||
}
|
||||
|
||||
@@ -343,7 +372,11 @@ fn fake_cargo(dir: &Path, script: &str) -> String {
|
||||
let path = dir.join("cargo");
|
||||
std::fs::write(&path, script).unwrap();
|
||||
std::fs::set_permissions(&path, std::fs::Permissions::from_mode(0o755)).unwrap();
|
||||
format!("{}:{}", dir.display(), std::env::var("PATH").unwrap_or_default())
|
||||
format!(
|
||||
"{}:{}",
|
||||
dir.display(),
|
||||
std::env::var("PATH").unwrap_or_default()
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -442,7 +475,10 @@ sys.exit(19)
|
||||
};
|
||||
write_frame(
|
||||
&mut stream,
|
||||
&Response::success(request.id, json!({"handle":handle,"progress":"cargo-json"})),
|
||||
&Response::success(
|
||||
request.id,
|
||||
json!({"handle":handle,"progress":"cargo-json"}),
|
||||
),
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
@@ -457,19 +493,32 @@ sys.exit(19)
|
||||
}
|
||||
panic!("completion was not published");
|
||||
});
|
||||
let output = ctl(&["--socket", socket.to_str().unwrap(), "run", "--", "cargo", "build"])
|
||||
.env("PATH", path)
|
||||
.current_dir(dir.path())
|
||||
.output()
|
||||
.unwrap();
|
||||
let output = ctl(&[
|
||||
"--socket",
|
||||
socket.to_str().unwrap(),
|
||||
"run",
|
||||
"--",
|
||||
"cargo",
|
||||
"build",
|
||||
])
|
||||
.env("PATH", path)
|
||||
.current_dir(dir.path())
|
||||
.output()
|
||||
.unwrap();
|
||||
assert_eq!(output.status.code(), Some(19));
|
||||
assert_eq!(output.stdout, b"child output\ndiagnostic\n");
|
||||
assert!(server.join().unwrap() > 0, "probe must exercise progress publishing");
|
||||
assert!(
|
||||
server.join().unwrap() > 0,
|
||||
"probe must exercise progress publishing"
|
||||
);
|
||||
let elapsed: f64 = std::fs::read_to_string(dir.path().join("elapsed"))
|
||||
.unwrap()
|
||||
.parse()
|
||||
.unwrap();
|
||||
assert!(elapsed < 1.5, "stdout reader stalled the child for {elapsed}s");
|
||||
assert!(
|
||||
elapsed < 1.5,
|
||||
"stdout reader stalled the child for {elapsed}s"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@@ -488,7 +537,9 @@ fn launcher_toggle_uses_the_shell_command() {
|
||||
|
||||
#[test]
|
||||
fn pomodoro_commands_use_the_versioned_session_api() {
|
||||
for action in ["start", "pause", "resume", "next", "stop", "reset", "status"] {
|
||||
for action in [
|
||||
"start", "pause", "resume", "next", "stop", "reset", "status",
|
||||
] {
|
||||
let directory = tempfile::tempdir().unwrap();
|
||||
let socket = directory.path().join("control.sock");
|
||||
let listener = UnixListener::bind(&socket).unwrap();
|
||||
@@ -496,8 +547,15 @@ fn pomodoro_commands_use_the_versioned_session_api() {
|
||||
let (mut stream, _) = listener.accept().unwrap();
|
||||
let request: Request = read_frame(&mut stream).unwrap();
|
||||
assert_eq!(request.command, "pomodoro");
|
||||
assert_eq!(request.args, json!({"version":1,"action":action,"session":null}));
|
||||
write_frame(&mut stream, &Response::success(request.id, json!({"phase":"work"}))).unwrap();
|
||||
assert_eq!(
|
||||
request.args,
|
||||
json!({"version":1,"action":action,"session":null})
|
||||
);
|
||||
write_frame(
|
||||
&mut stream,
|
||||
&Response::success(request.id, json!({"phase":"work"})),
|
||||
)
|
||||
.unwrap();
|
||||
});
|
||||
success(
|
||||
ctl(&["--socket", socket.to_str().unwrap(), "pomodoro", action])
|
||||
|
||||
Reference in new issue
Block a user