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
110 changes: 69 additions & 41 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down Expand Up @@ -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",
Expand Down Expand Up @@ -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.
Expand All @@ -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.
Expand All @@ -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});
},
}
}
Expand Down Expand Up @@ -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;
Comment thread
lujobi marked this conversation as resolved.
Outdated
},
.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);
Comment thread
lujobi marked this conversation as resolved.
Outdated
try self.queueRender();

// Consume the event such that the context menu is not displayed.
return true;
}

return false;
Expand Down
1 change: 1 addition & 0 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
32 changes: 32 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1886,6 +1886,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
Expand Down Expand Up @@ -6695,6 +6708,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,
Expand Down