use crate::app_state::{Screen, ScreenKind};
use crate::commands::{ArgKind, CommandArg, CommandRegistry, command};
use crate::issue_registry::Severity;
use crate::operation::{ClipOp, GoToLineOp, Operation, PersistentIssueOp, SearchOp, SelectionOp};
use crate::persistent_issues::PersistentNewIssue;
use crate::vcs::Vcs;
pub fn register_all(registry: &mut CommandRegistry) {
register_app(registry);
register_search(registry);
register_view(registry);
register_editor(registry);
register_file_selector(registry);
register_debug(registry);
register_vcs(registry);
register_todos(registry);
}
pub fn register_tasks(registry: &mut CommandRegistry, app: &crate::app_state::AppState) {
if let Some(cfg) = &app.task_config {
register_tasks_from_tasksfile(registry, cfg);
}
}
pub fn register_tasks_from_tasksfile(registry: &mut CommandRegistry, cfg: &crate::task_config::TasksFile) {
use std::borrow::Cow;
use std::collections::HashMap;
use crate::task_config::{TaskDef, InputType};
use crate::task_registry::{TaskKey, TaskQueueId, TaskTrigger};
use crate::commands::ArgValue as CmdArgValue;
for (task_id, task_def) in cfg.iter() {
if let TaskDef::Shell(shell) = task_def {
let id = crate::commands::id::CommandId {
group: Cow::Owned("task".to_string()),
name: Cow::Owned(task_id.to_string()),
};
let title = shell
.ui
.as_ref()
.and_then(|u| u.title.clone())
.unwrap_or_else(|| format!("Run {}", task_id));
let description = shell
.ui
.as_ref()
.and_then(|u| u.description.clone())
.unwrap_or_default();
let mut builder = crate::commands::command(id)
.title(title)
.description(description)
.context("global");
if let Some(inputs) = &shell.inputs {
for (name, input_def) in inputs {
let arg_kind = match input_def.input_type {
InputType::Text => crate::commands::ArgKind::String,
InputType::Select => {
let opts = input_def
.options
.as_ref()
.map(|v| v.iter().map(|s| s.clone().into()).collect::<Vec<std::borrow::Cow<'static, str>>>())
.unwrap_or_default();
crate::commands::ArgKind::Choice(opts)
}
InputType::Boolean => crate::commands::ArgKind::Bool,
InputType::Number => crate::commands::ArgKind::Int,
};
let desc = input_def.placeholder.clone().unwrap_or_default();
let required = input_def.default.is_none();
builder = builder.arg(crate::commands::CommandArg::new(
name.clone(),
desc,
arg_kind,
required,
));
}
}
let shell_clone = shell.clone();
let task_id_owned = task_id.to_string();
let cmd = builder.handler(move |_app, args| {
let mut values: HashMap<String, String> = HashMap::new();
if let Some(inputs_def) = &shell_clone.inputs {
for (input_name, input_def) in inputs_def {
let val = match args.get(input_name.as_str()) {
Some(v) => match v {
CmdArgValue::String(s) => s.clone(),
CmdArgValue::Bool(b) => b.to_string(),
CmdArgValue::Int(i) => i.to_string(),
CmdArgValue::FilePath(p) => p.to_string_lossy().into_owned(),
},
None => input_def
.default
.as_ref()
.map(|d| d.to_string_value())
.unwrap_or_default(),
};
values.insert(input_name.clone(), val);
}
}
let mut command_str = shell_clone.command.clone();
for (k, v) in &values {
command_str = command_str.replace(&format!("{{{}}}", k), v);
}
let queue = shell_clone.effective_queue(&task_id_owned).to_string();
let key = TaskKey {
queue: TaskQueueId(queue),
target: task_id_owned.clone(),
};
vec![crate::operation::Operation::ScheduleTask {
key,
trigger: TaskTrigger::Manual,
command: command_str,
}]
});
registry.register(cmd);
}
}
}
fn register_app(registry: &mut CommandRegistry) {
registry.register(
command("app.quit")
.title("Quit")
.description("Exit the application")
.context("global")
.handler(|_, _| vec![Operation::Quit]),
);
}
fn register_search(registry: &mut CommandRegistry) {
registry.register(
command("search.open_global")
.title("Global Search")
.description("Open the project-wide search in the editor (Expanded mode)")
.context("global")
.handler(|_, _| {
vec![
Operation::SwitchScreen { to: ScreenKind::Editor },
Operation::SearchLocal(SearchOp::Open { replace: false }),
]
}),
);
}
fn register_view(registry: &mut CommandRegistry) {
registry.register(
command("view.open_file_selector")
.title("Open File Selector")
.description("Switch to the file selector screen")
.context("global")
.hidden()
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::FileSelector,
}]
}),
);
registry.register(
command("view.open_command_runner")
.title("Open Command Runner")
.description("Open the command palette to search and run commands")
.context("global")
.hidden()
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::CommandRunner,
}]
}),
);
registry.register(
command("terminal.open")
.title("Open Terminal")
.description("Switch to the integrated terminal view")
.context("global")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::Terminal,
}]
}),
);
registry.register(
command("view.open_task_archive")
.title("Open Task Archive")
.description("Open the Task Archive view to browse running and finished tasks")
.context("global")
.handler(|_, _| {
vec![Operation::SwitchScreen { to: ScreenKind::TaskArchive }]
}),
);
registry.register(
command("view.open_issues")
.title("Open Issue List")
.description("Open the Issue List view to browse all diagnostics and TODOs")
.context("global")
.handler(|_, _| {
vec![Operation::OpenIssueList]
}),
);
registry.register(
command("todo.view")
.title("Open TODO List")
.description("Open the Issue List view to browse TODOs and diagnostics")
.context("global")
.handler(|_, _| {
vec![Operation::OpenIssueList]
}),
);
registry.register(
command("terminal.open_tab_selector")
.title("Select Terminal Tab")
.description("Open the tab switcher in the terminal view")
.context("terminal")
.hidden()
.handler(|_, _| {
vec![Operation::TerminalLocal(
crate::operation::TerminalOp::OpenTabSelector,
)]
}),
);
registry.register(
command("terminal.new_tab")
.title("New Terminal Tab")
.description("Open a new terminal tab with the default shell")
.context("terminal")
.handler(|_, _| {
vec![Operation::TerminalLocal(
crate::operation::TerminalOp::NewTab { command: None },
)]
}),
);
registry.register(
command("terminal.close_active_tab")
.title("Close Terminal Tab")
.description("Close the currently active terminal tab")
.context("terminal")
.hidden()
.handler(|app, _| {
if let Screen::Terminal(tv) = &app.screen
&& let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
return vec![Operation::TerminalLocal(
crate::operation::TerminalOp::CloseTab { id },
)];
}
vec![]
}),
);
registry.register(
command("terminal.rename_active_tab")
.title("Rename Terminal Tab")
.description("Rename the currently active terminal tab")
.context("terminal")
.hidden()
.handler(|app, _| {
if let Screen::Terminal(tv) = &app.screen
&& let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
return vec![Operation::TerminalLocal(
crate::operation::TerminalOp::OpenRename { id },
)];
}
vec![]
}),
);
registry.register(
command("terminal.interrupt")
.title("Send Ctrl+C")
.description("Send interrupt signal (^C / ETX) to the active terminal process")
.context("terminal")
.hidden()
.handler(|app, _| {
if let Screen::Terminal(tv) = &app.screen
&& let Some(id) = tv.active_id()
{
return vec![
Operation::TerminalLocal(crate::operation::TerminalOp::ScrollReset),
Operation::TerminalInput { id, data: vec![0x03] },
];
}
vec![]
}),
);
registry.register(
command("terminal.clear_active_tab")
.title("Clear Terminal")
.description("Clear the screen and scrollback of the active terminal tab")
.context("terminal")
.handler(|app, _| {
if let Screen::Terminal(tv) = &app.screen
&& let Some(id) = tv.tabs.get(tv.active).map(|t| t.id) {
return vec![Operation::TerminalLocal(
crate::operation::TerminalOp::ClearTab { id },
)];
}
vec![]
}),
);
registry.register(
command("extensions.config")
.title("Extensions Settings")
.description("Configure installed extensions")
.context("global")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::ExtensionConfig,
}]
}),
);
registry.register(
command("app.close_modal")
.title("Close / Dismiss")
.description("Close the topmost overlay, sub-view, or modal (completion menu, search bar, detail pane, …)")
.context("global")
.hidden()
.handler(|_, _| {
vec![Operation::Close]
}),
);
registry.register(
command("app.go_to")
.title("Go To")
.description("Go to symbol definition or file line depending on context")
.context("global")
.hidden()
.handler(|app, _| {
use crate::views::commit::CommitTab;
match &app.screen {
Screen::Editor(_) => vec![Operation::GoTo {
target: crate::operation::GoToTarget::LspRequest,
}],
Screen::CommitWindow(cw) => {
if cw.tab == CommitTab::Diff
&& let Some(path) = cw.diff_file.clone() {
let line = cw.diff_jump_line();
return vec![Operation::GoTo {
target: crate::operation::GoToTarget::FileLine {
path,
line,
col: None,
},
}];
}
if let Some(path) = cw.selected_file_path() {
return vec![Operation::GoTo {
target: crate::operation::GoToTarget::FileLine {
path,
line: 0,
col: None,
},
}];
}
vec![]
}
Screen::FileSelector(fs) => {
if let Some(path) = fs.selected_path() {
return vec![Operation::GoTo {
target: crate::operation::GoToTarget::FileLine {
path: path.clone(),
line: 0,
col: None,
},
}];
}
vec![]
}
Screen::GitHistory(gh) => {
if let Some(path) = gh.selected_file_path().map(|p| p.to_path_buf()) {
if let Some(diff) = &gh.diff
&& let Some(line_no) = diff.selected_line_no() {
return vec![Operation::GoTo {
target: crate::operation::GoToTarget::FileLine {
path,
line: line_no.saturating_sub(1),
col: None,
},
}];
}
return vec![Operation::GoTo {
target: crate::operation::GoToTarget::FileLine {
path,
line: 0,
col: None,
},
}];
}
vec![]
}
_ => vec![Operation::GoTo {
target: crate::operation::GoToTarget::LspRequest,
}],
}
}),
);
}
fn register_editor(registry: &mut CommandRegistry) {
registry.register(
command("editor.save")
.title("Save File")
.description("Write the current buffer to disk")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen
&& let Some(path) = ed.buffer.path.clone()
{
return vec![Operation::SaveFile { path }];
}
vec![]
}),
);
registry.register(
command("editor.reload")
.title("Reload File")
.description("Reload the current file from disk")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen
&& let Some(path) = ed.buffer.path.clone()
{
return vec![Operation::ReloadFromDisk {
path,
accept_external: false,
silent: false,
}];
}
vec![]
}),
);
registry.register(
command("editor.undo")
.title("Undo")
.description("Undo the last edit")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::Undo {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.redo")
.title("Redo")
.description("Redo the last undone edit")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::Redo {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.toggle_fold")
.title("Toggle Fold")
.description("Collapse or expand the fold at the cursor line")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::ToggleFold {
path: ed.buffer.path.clone(),
line: ed.buffer.cursor().line,
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.toggle_marker")
.title("Toggle Marker")
.description("Place or remove a marker on the cursor line")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::ToggleMarker {
path: ed.buffer.path.clone(),
line: ed.buffer.cursor().line,
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.copy")
.title("Copy")
.description("Copy selected text (or current line) to clipboard")
.context("editor")
.handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Copy)]),
);
registry.register(
command("editor.cut")
.title("Cut")
.description("Cut selected text (or current line) to clipboard")
.context("editor")
.handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Cut)]),
);
registry.register(
command("editor.paste")
.title("Paste")
.description("Paste from system clipboard")
.context("editor")
.handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::Paste)]),
);
registry.register(
command("paste.history")
.title("Paste from History")
.description("Open clipboard history picker to paste a previous entry")
.context("editor")
.handler(|_, _| vec![Operation::ClipboardLocal(ClipOp::OpenHistory)]),
);
registry.register(
command("editor.find")
.title("Find")
.description("Open the find bar")
.context("editor")
.handler(|_, _| vec![Operation::SearchLocal(SearchOp::Open { replace: false })]),
);
registry.register(
command("editor.find_replace")
.title("Find & Replace")
.description("Open the find and replace bar")
.context("editor")
.handler(|_, _| vec![Operation::SearchLocal(SearchOp::Open { replace: true })]),
);
registry.register(
command("editor.find_next")
.title("Find Next")
.description("Jump to the next search match")
.context("editor")
.handler(|_, _| vec![Operation::SearchLocal(SearchOp::NextMatch)]),
);
registry.register(
command("editor.find_prev")
.title("Find Previous")
.description("Jump to the previous search match")
.context("editor")
.handler(|_, _| vec![Operation::SearchLocal(SearchOp::PrevMatch)]),
);
registry.register(
command("editor.go_to_line")
.title("Go to Line")
.description("Jump to a specific line number")
.context("editor")
.handler(|_, _| vec![Operation::GoToLineLocal(GoToLineOp::Open)]),
);
registry.register(
command("editor.toggle_word_wrap")
.title("Toggle Word Wrap")
.description("Toggle soft word-wrapping of long lines")
.context("editor")
.handler(|_, _| vec![Operation::ToggleWordWrap]),
);
registry.register(
command("editor.delete_word_backward")
.title("Delete Word Backward")
.description("Delete from the start of the previous word to the cursor")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::DeleteWordBackward {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.delete_word_forward")
.title("Delete Word Forward")
.description("Delete from the cursor to the start of the next word")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::DeleteWordForward {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.delete_line")
.title("Delete Line")
.description("Delete the current line")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::DeleteLine {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.indent")
.title("Indent Lines")
.description("Add indentation to current line or selected lines")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::IndentLines {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.unindent")
.title("Unindent Lines")
.description("Remove indentation from current line or selected lines")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::UnindentLines {
path: ed.buffer.path.clone(),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.word_next")
.title("Jump to Next Word")
.description("Move the cursor to the start of the next word")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::MoveCursor {
path: ed.buffer.path.clone(),
cursor: ed.buffer.word_boundary_next(ed.buffer.cursor()),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.word_prev")
.title("Jump to Previous Word")
.description("Move the cursor to the start of the previous word")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::MoveCursor {
path: ed.buffer.path.clone(),
cursor: ed.buffer.word_boundary_prev(ed.buffer.cursor()),
}]
} else {
vec![]
}
}),
);
registry.register(
command("editor.select_word_next")
.title("Select to Next Word")
.description("Extend the selection to the start of the next word")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::SelectionLocal(SelectionOp::Extend {
head: ed.buffer.word_boundary_next_for_selection(ed.buffer.cursor()),
})]
} else {
vec![]
}
}),
);
registry.register(
command("editor.select_word_prev")
.title("Select to Previous Word")
.description("Extend the selection to the start of the previous word")
.context("editor")
.handler(|app, _| {
if let Screen::Editor(ed) = &app.screen {
vec![Operation::SelectionLocal(SelectionOp::Extend {
head: ed.buffer.word_boundary_prev(ed.buffer.cursor()),
})]
} else {
vec![]
}
}),
);
registry.register(
command("editor.clear_selection")
.title("Clear Selection")
.description("Deselect the current selection without moving the cursor")
.context("editor")
.handler(|_, _| vec![Operation::SelectionLocal(SelectionOp::Clear)]),
);
registry.register(
command("editor.select_all")
.title("Select All")
.description("Select all text in the current buffer")
.context("editor")
.handler(|_, _| vec![Operation::SelectionLocal(SelectionOp::SelectAll)]),
);
registry.register(
command("editor.toggle_comment")
.title("Toggle Comment")
.description("Toggle comment for selected lines or current line")
.context("editor")
.handler(|_, _| {
vec![Operation::ShowNotification {
message: "Toggle comment not implemented".into(),
level: crate::operation::NotificationLevel::Info,
}]
}),
);
registry.register(
command("editor.format_document")
.title("Format Document")
.description("Format current buffer (not implemented)")
.context("editor")
.handler(|_, _| {
vec![Operation::ShowNotification {
message: "Format not implemented".into(),
level: crate::operation::NotificationLevel::Info,
}]
}),
);
}
fn register_debug(registry: &mut CommandRegistry) {
registry.register(
command("debug.show_log_view")
.title("Show Log View")
.description("Open the IDE log viewer")
.context("global")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::LogView,
}]
}),
);
}
fn register_file_selector(registry: &mut CommandRegistry) {
registry.register(
command("file_selector.open")
.title("Open Selected File")
.description("Open the file currently selected in the file selector")
.context("file_selector")
.hidden()
.handler(|app, _| {
if let Screen::FileSelector(fs) = &app.screen
&& let Some(path) = fs.selected_path()
{
return vec![Operation::OpenFile { path }];
}
vec![]
}),
);
}
fn register_vcs(registry: &mut CommandRegistry) {
registry.register(
command("vcs.open_commit")
.title("Open Commit Window")
.description("Stage files, manage branches, and commit changes [Ctrl+G]")
.context("git_repo")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::CommitWindow,
}]
}),
);
registry.register(
command("vcs.history")
.title("Open Git History")
.description("Browse commit history with filterable revisions and diff viewer")
.context("git_repo")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::GitHistory,
}]
}),
);
registry.register(
command("vcs.history_editor")
.title("Git: History Editor")
.description("Interactively reword, move, squash, or drop commits")
.context("git_repo")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::GitHistoryEditor,
}]
}),
);
registry.register(
command("vcs.branches")
.title("Git: Branches")
.description("Browse and manage local/remote branches — checkout or create branches")
.context("git_repo")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::GitBranches,
}]
}),
);
registry.register(
command("vcs.open_branch_selector")
.title("Open Branch Selector")
.description("Open a branch selector popup to switch branches")
.context("git_repo")
.hidden()
.handler(|_, _| {
vec![Operation::CommitLocal(crate::operation::CommitOp::BranchSelectorOpen)]
}),
);
registry.register(
command("vcs.amend_last_commit")
.title("Amend Last Commit")
.description("Open Commit Window and load the last commit message for amendment")
.context("git_repo")
.handler(|_, _| {
vec![
Operation::SwitchScreen {
to: ScreenKind::CommitWindow,
},
Operation::CommitLocal(crate::operation::CommitOp::EnterAmendMode),
]
}),
);
registry.register(
command("vcs.pull")
.title("Git: Pull")
.description(
"Fetch from remote then fast-forward, rebase, or merge — \
your explicit choice when history diverges",
)
.context("git_repo")
.handler(|_, _| {
vec![Operation::SwitchScreen {
to: ScreenKind::GitPull,
}]
}),
);
registry.register(
command("vcs.push")
.title("Git: Push")
.description("Push current branch to configured remote (background task)")
.context("git_repo")
.arg(CommandArg::new("force", "Force push (use --force-with-lease)", ArgKind::Bool, false))
.handler(|app, args| {
use crate::commands::ArgValue;
let force = match args.get("force") {
Some(ArgValue::Bool(b)) => *b,
_ => false,
};
let root = app.project.project_path.clone();
let root_str = root.to_string_lossy().into_owned();
let safe_root = root_str.replace('"', "\\\"");
let mut command = if force {
format!("git -C \"{}\" push --force-with-lease", safe_root)
} else {
format!("git -C \"{}\" push", safe_root)
};
let key = if let Ok(v) = crate::vcs::GitVcs::open(&root) {
if let Ok((_branches, current_branch)) = v.branches() {
if !current_branch.is_empty() && !current_branch.starts_with('(') {
let remote = std::process::Command::new("git")
.args(["-C", root_str.as_str(), "config", "--get", &format!("branch.{}.remote", current_branch)])
.output()
.ok()
.and_then(|o| if o.status.success() {
Some(String::from_utf8_lossy(&o.stdout).trim().to_string())
} else {
None
})
.filter(|s| !s.is_empty())
.unwrap_or_else(|| "origin".to_string());
if force {
command = format!("git -C \"{}\" push --force-with-lease {} {}", safe_root, remote, current_branch);
} else {
command = format!("git -C \"{}\" push {} {}", safe_root, remote, current_branch);
}
crate::task_registry::TaskKey {
queue: crate::task_registry::TaskQueueId("git".into()),
target: format!("push:{}:{}{}", root_str, current_branch, if force { ":force" } else { "" }),
}
} else {
crate::task_registry::TaskKey {
queue: crate::task_registry::TaskQueueId("git".into()),
target: format!("{}{}", root_str, if force { ":force" } else { "" }),
}
}
} else {
crate::task_registry::TaskKey {
queue: crate::task_registry::TaskQueueId("git".into()),
target: format!("{}{}", root_str, if force { ":force" } else { "" }),
}
}
} else {
crate::task_registry::TaskKey {
queue: crate::task_registry::TaskQueueId("git".into()),
target: format!("{}{}", root_str, if force { ":force" } else { "" }),
}
};
vec![Operation::ScheduleTask {
key,
trigger: crate::task_registry::TaskTrigger::Manual,
command,
}]
}),
);
}
fn register_todos(registry: &mut CommandRegistry) {
registry.register(
command("todo.create")
.title("Create TODO")
.description("Create a new persistent TODO saved to .oo/issues.yaml")
.context("global")
.arg(CommandArg::new(
"title",
"Short description of the TODO",
ArgKind::String,
true,
))
.arg(CommandArg::new(
"severity",
"Severity level: info, warning, or error",
ArgKind::Choice(vec!["info".into(), "warning".into(), "error".into()]),
false,
))
.handler(|_, args| {
use crate::commands::ArgValue;
let Some(ArgValue::String(title)) = args.get("title") else {
return vec![];
};
let severity = match args.get("severity") {
Some(ArgValue::String(s)) => match s.as_str() {
"warning" => Severity::Warning,
"error" => Severity::Error,
_ => Severity::Info,
},
_ => Severity::Info,
};
vec![Operation::PersistentIssueLocal(PersistentIssueOp::Add {
issue: PersistentNewIssue {
source: "user".into(),
path: None,
range: None,
message: title.clone(),
severity,
},
})]
}),
);
registry.register(
command("todo.resolve")
.title("Resolve TODO")
.description("Mark a persistent TODO as resolved")
.context("global")
.arg(CommandArg::new(
"id",
"Numeric ID of the TODO to resolve",
ArgKind::Int,
true,
))
.handler(|_, args| {
use crate::commands::ArgValue;
use crate::issue_registry::IssueId;
let Some(ArgValue::Int(raw_id)) = args.get("id") else {
return vec![];
};
let id = *raw_id as IssueId;
vec![Operation::PersistentIssueLocal(PersistentIssueOp::Resolve { id })]
}),
);
registry.register(
command("todo.dismiss")
.title("Dismiss TODO")
.description("Dismiss a persistent TODO without resolving it")
.context("global")
.arg(CommandArg::new(
"id",
"Numeric ID of the TODO to dismiss",
ArgKind::Int,
true,
))
.handler(|_, args| {
use crate::commands::ArgValue;
use crate::issue_registry::IssueId;
let Some(ArgValue::Int(raw_id)) = args.get("id") else {
return vec![];
};
let id = *raw_id as IssueId;
vec![Operation::PersistentIssueLocal(PersistentIssueOp::Dismiss { id })]
}),
);
}