mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 16:56:09 +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__
|
#ifdef __APPLE__
|
||||||
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
void ghostty_surface_set_display_id(ghostty_surface_t, uint32_t);
|
||||||
void* ghostty_surface_quicklook_font(ghostty_surface_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*);
|
bool ghostty_surface_selection_info(ghostty_surface_t, ghostty_selection_s*);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
@ -623,7 +623,7 @@ extension Ghostty {
|
|||||||
guard UserDefaults.standard.bool(forKey: "com.apple.trackpad.forceClick") else { return }
|
guard UserDefaults.standard.bool(forKey: "com.apple.trackpad.forceClick") else { return }
|
||||||
quickLook(with: event)
|
quickLook(with: event)
|
||||||
}
|
}
|
||||||
|
|
||||||
override func cursorUpdate(with event: NSEvent) {
|
override func cursorUpdate(with event: NSEvent) {
|
||||||
switch (cursorVisible) {
|
switch (cursorVisible) {
|
||||||
case .visible, .hidden:
|
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? {
|
override func menu(for event: NSEvent) -> NSMenu? {
|
||||||
// We only support right-click menus
|
// We only support right-click menus
|
||||||
switch event.type {
|
switch event.type {
|
||||||
|
@ -3061,7 +3061,7 @@ pub fn colorSchemeCallback(self: *Surface, scheme: apprt.ColorScheme) !void {
|
|||||||
if (report) try self.reportColorScheme();
|
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
|
// xpos/ypos need to be adjusted for window padding
|
||||||
// (i.e. "window-padding-*" settings.
|
// (i.e. "window-padding-*" settings.
|
||||||
const pad = if (self.config.window_padding_balance)
|
const pad = if (self.config.window_padding_balance)
|
||||||
|
@ -1829,6 +1829,61 @@ pub const CAPI = struct {
|
|||||||
return copy;
|
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 returns the selection metadata for the current selection.
|
||||||
/// This will return false if there is no selection or the
|
/// This will return false if there is no selection or the
|
||||||
/// selection is not fully contained in the viewport (since the
|
/// selection is not fully contained in the viewport (since the
|
||||||
|
Reference in New Issue
Block a user