Merge pull request #1908 from ghostty-org/macosql

macos: implement ctrl+command+d for quicklook under cursor
This commit is contained in:
Mitchell Hashimoto
2024-07-01 10:12:22 -07:00
committed by GitHub
4 changed files with 91 additions and 2 deletions

View File

@ -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

View File

@ -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 {

View File

@ -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)

View File

@ -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