Add --before/--after position flags to task edit
Lets you reposition a task in the same command that changes its
state or title, avoiding a separate `task move` call.
Assisted-by: Claude Opus 4.6 via pi
diff --git a/src/bin/ranger/commands/task.rs b/src/bin/ranger/commands/task.rs
index e03b4bc..7c37a50 100644
--- a/src/bin/ranger/commands/task.rs
+++ b/src/bin/ranger/commands/task.rs
@@ -88,6 +88,8 @@ pub enum TaskCommands {
/// New state
#[arg(long)]
state: Option<String>,
+ #[command(flatten)]
+ position: PositionArgs,
},
/// Move a task's position within its backlog
#[command(visible_alias = "mv")]
@@ -222,11 +224,14 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
title,
description,
state,
+ position,
} => {
let mut conn = pool.acquire().await?;
let state = state.map(|s| s.parse::<State>()).transpose()?;
let task = ops::task::get_by_key_prefix(&mut conn, &key).await?;
+ let (before_id, after_id) = position.resolve(&mut conn).await?;
+
let updated = ops::task::edit(
&mut conn,
task.id,
@@ -235,6 +240,11 @@ pub async fn run(pool: &SqlitePool, command: TaskCommands, json: bool) -> Result
state,
)
.await?;
+
+ if before_id.is_some() || after_id.is_some() {
+ ops::task::move_task(&mut conn, updated.id, before_id, after_id).await?;
+ }
+
output::print(&updated, json, print_task);
}
TaskCommands::Move { key, position } => {
diff --git a/tests/cli.rs b/tests/cli.rs
index 500d385..491ea14 100644
--- a/tests/cli.rs
+++ b/tests/cli.rs
@@ -110,6 +110,68 @@ fn full_workflow() {
assert_eq!(detail["tags"][0]["name"], "urgent");
assert_eq!(detail["blockers"].as_array().unwrap().len(), 1);
+ // Create a third task and use edit --before to reposition it
+ let output = ranger(db_path)
+ .args(["task", "create", "Third task", "--state", "queued"])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+
+ let output = ranger(db_path)
+ .args(["task", "list", "--json", "--state", "queued"])
+ .output()
+ .unwrap();
+ let tasks_before_move: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+ let tasks_before_move = tasks_before_move.as_array().unwrap();
+ // Third task should be after Second task (both queued, but Second was icebox — actually
+ // let's just get the keys and verify edit --before works)
+ let t3_key = tasks_before_move
+ .iter()
+ .find(|t| t["title"] == "Third task")
+ .unwrap()["key"]
+ .as_str()
+ .unwrap()
+ .to_string();
+
+ // Edit Third task: change title AND reposition before First task
+ let output = ranger(db_path)
+ .args([
+ "task",
+ "edit",
+ &t3_key[..4],
+ "--title",
+ "Third task (edited)",
+ "--before",
+ &t1_key[..4],
+ ])
+ .output()
+ .unwrap();
+ assert!(output.status.success());
+ let stdout = String::from_utf8(output.stdout).unwrap();
+ assert!(stdout.contains("Third task (edited)"));
+
+ // Verify ordering: Third should now be before First
+ let output = ranger(db_path)
+ .args(["task", "list", "--json"])
+ .output()
+ .unwrap();
+ let tasks_after_move: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
+ let tasks_after_move = tasks_after_move.as_array().unwrap();
+ let titles: Vec<&str> = tasks_after_move
+ .iter()
+ .map(|t| t["title"].as_str().unwrap())
+ .collect();
+ let third_pos = titles
+ .iter()
+ .position(|t| *t == "Third task (edited)")
+ .unwrap();
+ let first_pos = titles.iter().position(|t| *t == "First task").unwrap();
+ assert!(
+ third_pos < first_pos,
+ "Third task should be before First task after edit --before, got: {:?}",
+ titles
+ );
+
// Delete a task
let output = ranger(db_path)
.args(["task", "delete", &t2_key[..4]])
@@ -124,5 +186,5 @@ fn full_workflow() {
.unwrap();
assert!(output.status.success());
let tasks: serde_json::Value = serde_json::from_slice(&output.stdout).unwrap();
- assert_eq!(tasks.as_array().unwrap().len(), 1);
+ assert_eq!(tasks.as_array().unwrap().len(), 2);
}