mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #1908 from ghostty-org/macosql
macos: implement ctrl+command+d for quicklook under cursor
This commit is contained in:
@ -565,6 +565,10 @@ uintptr_t ghostty_surface_selection(ghostty_surface_t, char*, uintptr_t);
|
||||
#ifdef __APPLE__
|
||||
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
||||
void* ghostty_surface_quicklook_font(ghostty_surface_t);
|
||||
uintptr_t ghostty_surface_quicklook_word(ghostty_surface_t,
|
||||
char*,
|
||||
uintptr_t,
|
||||
ghostty_selection_s*);
|
||||
bool ghostty_surface_selection_info(ghostty_surface_t, ghostty_selection_s*);
|
||||
#endif
|
||||
|
||||
|
@ -623,7 +623,7 @@ extension Ghostty {
|
||||
guard UserDefaults.standard.bool(forKey: "com.apple.trackpad.forceClick") else { return }
|
||||
quickLook(with: event)
|
||||
}
|
||||
|
||||
|
||||
override func cursorUpdate(with event: NSEvent) {
|
||||
switch (cursorVisible) {
|
||||
case .visible, .hidden:
|
||||
@ -867,6 +867,36 @@ extension Ghostty {
|
||||
}
|
||||
}
|
||||
|
||||
override func quickLook(with event: NSEvent) {
|
||||
guard let surface = self.surface else { return super.quickLook(with: event) }
|
||||
|
||||
// Grab the text under the cursor
|
||||
var info: ghostty_selection_s = ghostty_selection_s();
|
||||
let text = String(unsafeUninitializedCapacity: 1000000) {
|
||||
Int(ghostty_surface_quicklook_word(surface, $0.baseAddress, UInt($0.count), &info))
|
||||
}
|
||||
guard !text.isEmpty else { return super.quickLook(with: event) }
|
||||
|
||||
// If we can get a font then we use the font. This should always work
|
||||
// since we always have a primary font. The only scenario this doesn't
|
||||
// work is if someone is using a non-CoreText build which would be
|
||||
// unofficial.
|
||||
var attributes: [ NSAttributedString.Key : Any ] = [:];
|
||||
if let fontRaw = ghostty_surface_quicklook_font(surface) {
|
||||
// Memory management here is wonky: ghostty_surface_quicklook_font
|
||||
// will create a copy of a CTFont, Swift will auto-retain the
|
||||
// unretained value passed into the dict, so we release the original.
|
||||
let font = Unmanaged<CTFont>.fromOpaque(fontRaw)
|
||||
attributes[.font] = font.takeUnretainedValue()
|
||||
font.release()
|
||||
}
|
||||
|
||||
// Ghostty coordinate system is top-left, conver to bottom-left for AppKit
|
||||
let pt = NSMakePoint(info.tl_px_x - 2, frame.size.height - info.tl_px_y + 2)
|
||||
let str = NSAttributedString.init(string: text, attributes: attributes)
|
||||
self.showDefinition(for: str, at: pt);
|
||||
}
|
||||
|
||||
override func menu(for event: NSEvent) -> NSMenu? {
|
||||
// We only support right-click menus
|
||||
switch event.type {
|
||||
|
@ -3061,7 +3061,7 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
|
||||
if (report) try self.reportColorScheme();
|
||||
}
|
||||
|
||||
fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate {
|
||||
pub fn posToViewport(self: Surface, xpos: f64, ypos: f64) terminal.point.Coordinate {
|
||||
// xpos/ypos need to be adjusted for window padding
|
||||
// (i.e. "window-padding-*" settings.
|
||||
const pad = if (self.config.window_padding_balance)
|
||||
|
@ -1829,6 +1829,61 @@ pub const CAPI = struct {
|
||||
return copy;
|
||||
}
|
||||
|
||||
/// This returns the selected word for quicklook. This will populate
|
||||
/// the buffer with the word under the cursor and the selection
|
||||
/// info so that quicklook can be rendered.
|
||||
///
|
||||
/// This does not modify the selection active on the surface (if any).
|
||||
export fn ghostty_surface_quicklook_word(
|
||||
ptr: *Surface,
|
||||
buf: [*]u8,
|
||||
cap: usize,
|
||||
info: *Selection,
|
||||
) usize {
|
||||
const surface = &ptr.core_surface;
|
||||
surface.renderer_state.mutex.lock();
|
||||
defer surface.renderer_state.mutex.unlock();
|
||||
|
||||
// To make everything in this function easier, we modify the
|
||||
// selection to be the word under the cursor and call normal APIs.
|
||||
// We restore the old selection so it isn't ever changed. Since we hold
|
||||
// the renderer mutex it'll never show up in a frame.
|
||||
const prev = surface.io.terminal.screen.selection;
|
||||
defer surface.io.terminal.screen.selection = prev;
|
||||
|
||||
// Get our word selection
|
||||
const sel = sel: {
|
||||
const screen = &surface.renderer_state.terminal.screen;
|
||||
const pos = try ptr.getCursorPos();
|
||||
const pt_viewport = surface.posToViewport(pos.x, pos.y);
|
||||
const pin = screen.pages.pin(.{
|
||||
.viewport = .{
|
||||
.x = pt_viewport.x,
|
||||
.y = pt_viewport.y,
|
||||
},
|
||||
}) orelse {
|
||||
if (comptime std.debug.runtime_safety) unreachable;
|
||||
return 0;
|
||||
};
|
||||
break :sel surface.io.terminal.screen.selectWord(pin) orelse return 0;
|
||||
};
|
||||
|
||||
// Set the selection
|
||||
surface.io.terminal.screen.selection = sel;
|
||||
|
||||
// No we call normal functions. These require that the lock
|
||||
// is unlocked. This may cause a frame flicker with the fake
|
||||
// selection but I think the lack of new complexity is worth it
|
||||
// for now.
|
||||
{
|
||||
surface.renderer_state.mutex.unlock();
|
||||
defer surface.renderer_state.mutex.lock();
|
||||
const len = ghostty_surface_selection(ptr, buf, cap);
|
||||
if (!ghostty_surface_selection_info(ptr, info)) return 0;
|
||||
return len;
|
||||
}
|
||||
}
|
||||
|
||||
/// This returns the selection metadata for the current selection.
|
||||
/// This will return false if there is no selection or the
|
||||
/// selection is not fully contained in the viewport (since the
|
||||
|
Reference in New Issue
Block a user