Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -266,6 +266,7 @@ A first-session cheatsheet. Press `?` inside tuicr for the full reference.
| `e` | Open focused file in `$EDITOR` |
| `y` | Copy review to clipboard |
| `:edit` | Open focused file in `$EDITOR` |
| `:copy-url` | Copy the open PR URL (PR mode) |
| `:submit` | Push review to GitHub, GitLab, or Bitbucket |
| `Tab` in `:` prompt | Complete or cycle commands |
| `?` | Toggle full help |
Expand Down
1 change: 1 addition & 0 deletions docs/KEYBINDINGS.md
Original file line number Diff line number Diff line change
Expand Up @@ -166,6 +166,7 @@ In command mode,
| `:e` (`:reload`) | Reload diff files |
| `:edit` | Open focused file in `$EDITOR` |
| `:clip` (`:export`) | Copy review to clipboard |
| `:copy-url` | Copy the open PR URL to clipboard (PR mode) |
| `:diff` | Toggle diff view (unified / side-by-side) |
| `:vim` / `:novim` (`:set vim` / `:set novim`) | Enable/toggle/disable vim modal editing in the comment box (overrides `comment_vim`) |
| `:commits` | Select commits to review |
Expand Down
72 changes: 50 additions & 22 deletions src/handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::forge::remote_comments::PrCommentsVisibility;
use crate::forge::submit::SubmitEvent;
use crate::input::Action;
use crate::model::{ClearScope, LineSide};
use crate::output::{export_to_clipboard, generate_export_content};
use crate::output::{copy_text_to_clipboard, export_to_clipboard, generate_export_content};
use crate::text_edit::{
delete_char_before, delete_word_before, next_char_boundary, prev_char_boundary,
};
Expand All @@ -28,6 +28,7 @@ const COMMAND_SPECS: &[CommandSpec] = &[
CommandSpec::new(&["e", "reload"], CommandKind::Reload),
CommandSpec::new(&["edit"], CommandKind::Edit),
CommandSpec::new(&["clip", "export"], CommandKind::Export),
CommandSpec::new(&["copy-url"], CommandKind::CopyUrl),
CommandSpec::new(
&["clear"],
CommandKind::Clear(ClearScope::CommentsAndReviewed),
Expand Down Expand Up @@ -119,6 +120,7 @@ enum CommandKind {
Reload,
Edit,
Export,
CopyUrl,
Clear(ClearScope),
Help,
Version,
Expand Down Expand Up @@ -357,6 +359,20 @@ fn handle_left_click(app: &mut App, pos: Position) {
}
}

fn handle_copy_pr_url(app: &mut App) {
let app::DiffSource::PullRequest(pr) = &app.diff_source else {
app.set_warning(":copy-url only applies in PR mode");
return;
};
let url = pr.url.clone();

match copy_text_to_clipboard(&url) {
Ok(true) => app.set_message("PR URL copied to clipboard (via terminal)"),
Ok(false) => app.set_message("PR URL copied to clipboard"),
Err(e) => app.set_warning(format!("Failed to copy PR URL: {e}")),
}
}

/// Export review: either to clipboard or set pending stdout output based on app.output_to_stdout.
/// When output_to_stdout is true, stores the content and sets should_quit.
fn handle_export(app: &mut App) {
Expand Down Expand Up @@ -573,27 +589,6 @@ fn command_spec_for(cmd: &str) -> Option<&'static CommandSpec> {
COMMAND_SPECS.iter().find(|spec| spec.names.contains(&cmd))
}

#[cfg(test)]
mod relative_line_number_command_tests {
use super::{CommandKind, command_spec_for};

#[test]
fn parses_relative_line_number_commands() {
assert_eq!(
command_spec_for("set relativenumber").map(|spec| spec.kind),
Some(CommandKind::SetRelativeLineNumbers(true))
);
assert_eq!(
command_spec_for("set norelativenumber").map(|spec| spec.kind),
Some(CommandKind::SetRelativeLineNumbers(false))
);
assert_eq!(
command_spec_for("set relativenumber!").map(|spec| spec.kind),
Some(CommandKind::ToggleRelativeLineNumbers)
);
}
}

/// CommandCompleter computes command-buffer replacements without mutating App.
struct CommandCompleter<'a> {
/// Registry whose command names are exposed as completion candidates.
Expand Down Expand Up @@ -802,6 +797,10 @@ fn dispatch_command(app: &mut App, kind: CommandKind) -> CommandAfterDispatch {
handle_export(app);
CommandAfterDispatch::ExitCommandMode
}
CommandKind::CopyUrl => {
handle_copy_pr_url(app);
CommandAfterDispatch::ExitCommandMode
}
CommandKind::Clear(scope) => {
app.clear_comments(scope);
CommandAfterDispatch::ExitCommandMode
Expand Down Expand Up @@ -1691,3 +1690,32 @@ pub fn handle_submit_confirm_action(app: &mut App, action: Action) {
_ => {}
}
}

#[cfg(test)]
mod command_tests {
use super::{CommandKind, command_spec_for};

#[test]
fn parses_relative_line_number_commands() {
assert_eq!(
command_spec_for("set relativenumber").map(|spec| spec.kind),
Some(CommandKind::SetRelativeLineNumbers(true))
);
assert_eq!(
command_spec_for("set norelativenumber").map(|spec| spec.kind),
Some(CommandKind::SetRelativeLineNumbers(false))
);
assert_eq!(
command_spec_for("set relativenumber!").map(|spec| spec.kind),
Some(CommandKind::ToggleRelativeLineNumbers)
);
}

#[test]
fn parses_copy_url_command() {
assert_eq!(
command_spec_for("copy-url").map(|spec| spec.kind),
Some(CommandKind::CopyUrl)
);
}
}
7 changes: 7 additions & 0 deletions src/ui/help_popup.rs
Original file line number Diff line number Diff line change
Expand Up @@ -639,6 +639,13 @@ pub fn render_help(frame: &mut Frame, app: &mut App) {
),
Span::raw("Copy review to clipboard"),
]),
Line::from(vec![
Span::styled(
" :copy-url ",
Style::default().add_modifier(Modifier::BOLD),
),
Span::raw("Copy the open PR URL to clipboard (PR mode)"),
]),
Line::from(vec![
Span::styled(
" :wrap ",
Expand Down
Loading