diff --git a/src/Surface.zig b/src/Surface.zig index 866505717..9d8ceafb0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -247,6 +247,7 @@ const DerivedConfig = struct { clipboard_paste_protection: bool, clipboard_paste_bracketed_safe: bool, copy_on_select: configpkg.CopyOnSelect, + right_click_action: configpkg.RightClickAction, confirm_close_surface: configpkg.ConfirmCloseSurface, cursor_click_to_move: bool, desktop_notifications: bool, @@ -314,6 +315,7 @@ const DerivedConfig = struct { .clipboard_paste_protection = config.@"clipboard-paste-protection", .clipboard_paste_bracketed_safe = config.@"clipboard-paste-bracketed-safe", .copy_on_select = config.@"copy-on-select", + .right_click_action = config.@"right-click-action", .confirm_close_surface = config.@"confirm-close-surface", .cursor_click_to_move = config.@"cursor-click-to-move", .desktop_notifications = config.@"desktop-notifications", @@ -1833,6 +1835,32 @@ fn clipboardWrite(self: *const Surface, data: []const u8, loc: apprt.Clipboard) }; } +fn copySelectionToClipboards( + self: *Surface, + sel: terminal.Selection, + clipboards: []const apprt.Clipboard, +) void { + const buf = self.io.terminal.screen.selectionString(self.alloc, .{ + .sel = sel, + .trim = self.config.clipboard_trim_trailing_spaces, + }) catch |err| { + log.err("error reading selection string err={}", .{err}); + return; + }; + defer self.alloc.free(buf); + + for (clipboards) |clipboard| self.rt_surface.setClipboardString( + buf, + clipboard, + false, + ) catch |err| { + log.err( + "error setting clipboard string clipboard={} err={}", + .{ clipboard, err }, + ); + }; +} + /// Set the selection contents. /// /// This must be called with the renderer mutex held. @@ -1850,33 +1878,12 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void { const sel = sel_ orelse return; if (prev_) |prev| if (sel.eql(prev)) return; - const buf = self.io.terminal.screen.selectionString(self.alloc, .{ - .sel = sel, - .trim = self.config.clipboard_trim_trailing_spaces, - }) catch |err| { - log.err("error reading selection string err={}", .{err}); - return; - }; - defer self.alloc.free(buf); - - // Set the clipboard. This is not super DRY but it is clear what - // we're doing for each setting without being clever. switch (self.config.copy_on_select) { .false => unreachable, // handled above with an early exit // Both standard and selection clipboards are set. .clipboard => { - const clipboards: []const apprt.Clipboard = &.{ .standard, .selection }; - for (clipboards) |clipboard| self.rt_surface.setClipboardString( - buf, - clipboard, - false, - ) catch |err| { - log.err( - "error setting clipboard string clipboard={} err={}", - .{ clipboard, err }, - ); - }; + self.copySelectionToClipboards(sel, &.{ .standard, .selection }); }, // The selection clipboard is set if supported, otherwise the standard. @@ -1885,17 +1892,7 @@ fn setSelection(self: *Surface, sel_: ?terminal.Selection) !void { .selection else .standard; - - self.rt_surface.setClipboardString( - buf, - clipboard, - false, - ) catch |err| { - log.err( - "error setting clipboard string clipboard={} err={}", - .{ clipboard, err }, - ); - }; + self.copySelectionToClipboards(sel, &.{clipboard}); }, } } @@ -3582,18 +3579,49 @@ pub fn mouseButtonCallback( break :pin pin; }; - // If we already have a selection and the selection contains - // where we clicked then we don't want to modify the selection. - if (self.io.terminal.screen.selection) |prev_sel| { - if (prev_sel.contains(screen, pin)) break :sel; + switch (self.config.right_click_action) { + .ignore => { + // Return early to skip clearing the selection. + try self.queueRender(); + return true; + }, + .copy => { + if (self.io.terminal.screen.selection) |sel| { + self.copySelectionToClipboards(sel, &.{.standard}); + } + }, + .@"copy-or-paste" => { + if (self.io.terminal.screen.selection) |sel| { + self.copySelectionToClipboards(sel, &.{.standard}); + } else { + try self.startClipboardRequest(.standard, .paste); + } + }, + .paste => { + try self.startClipboardRequest(.standard, .paste); + }, + .@"context-menu" => { + // If we already have a selection and the selection contains + // where we clicked then we don't want to modify the selection. + if (self.io.terminal.screen.selection) |prev_sel| { + if (prev_sel.contains(screen, pin)) break :sel; - // The selection doesn't contain our pin, so we create a new - // word selection where we clicked. + // The selection doesn't contain our pin, so we create a new + // word selection where we clicked. + } + + const sel = screen.selectWord(pin) orelse break :sel; + try self.setSelection(sel); + try self.queueRender(); + return false; + }, } - const sel = screen.selectWord(pin) orelse break :sel; - try self.setSelection(sel); + try self.setSelection(null); try self.queueRender(); + + // Consume the event such that the context menu is not displayed. + return true; } return false; diff --git a/src/config.zig b/src/config.zig index df4eee791..bcb48214d 100644 --- a/src/config.zig +++ b/src/config.zig @@ -19,6 +19,7 @@ pub const ClipboardAccess = Config.ClipboardAccess; pub const Command = Config.Command; pub const ConfirmCloseSurface = Config.ConfirmCloseSurface; pub const CopyOnSelect = Config.CopyOnSelect; +pub const RightClickAction = Config.RightClickAction; pub const CustomShaderAnimation = Config.CustomShaderAnimation; pub const FontSyntheticStyle = Config.FontSyntheticStyle; pub const FontShapingBreak = Config.FontShapingBreak; diff --git a/src/config/Config.zig b/src/config/Config.zig index 05b5828f0..5ac2d6617 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1900,6 +1900,19 @@ keybind: Keybinds = .{}, else => .false, }, +/// The action to take when the user right-clicks on the terminal surface. +/// +/// Valid values: +/// * `context-menu` - Show the context menu. +/// * `paste` - Paste the contents of the clipboard. +/// * `copy` - Copy the selected text to the clipboard. +/// * `copy-or-paste` - If there is a selection, copy the selected text to +/// the clipboard; otherwise, paste the contents of the clipboard. +/// * `ignore` - Do nothing, ignore the right-click. +/// +/// The default value is `context-menu`. +@"right-click-action": RightClickAction = .@"context-menu", + /// The time in milliseconds between clicks to consider a click a repeat /// (double, triple, etc.) or an entirely new single click. A value of zero will /// use a platform-specific default. The default on macOS is determined by the @@ -6709,6 +6722,25 @@ pub const CopyOnSelect = enum { clipboard, }; +/// Options for right-click actions. +pub const RightClickAction = enum { + /// No action is taken on right-click. + ignore, + + /// Pastes from the system clipboard. + paste, + + /// Copies the selected text to the system clipboard. + copy, + + /// Copies the selected text to the system clipboard and + /// pastes the clipboard if no text is selected. + @"copy-or-paste", + + /// Shows a context menu with options. + @"context-menu", +}; + /// Shell integration values pub const ShellIntegration = enum { none,