mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
macos: hacky API to get a CTFont for QuickLook
This commit is contained in:
@ -556,6 +556,7 @@ 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);
|
||||
#endif
|
||||
|
||||
ghostty_inspector_t ghostty_surface_inspector(ghostty_surface_t);
|
||||
|
@ -1,4 +1,5 @@
|
||||
import SwiftUI
|
||||
import CoreText
|
||||
import UserNotifications
|
||||
import GhosttyKit
|
||||
|
||||
@ -929,7 +930,13 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
}
|
||||
|
||||
func selectedRange() -> NSRange {
|
||||
return NSRange()
|
||||
guard let surface = self.surface else { return NSRange() }
|
||||
guard ghostty_surface_has_selection(surface) else { return NSRange() }
|
||||
|
||||
// If we have a selection, we just return a non-empty range. The actual
|
||||
// values are meaningless but the non-emptiness of it tells AppKit we
|
||||
// have a selection.
|
||||
return NSRange(location: 0, length: 1)
|
||||
}
|
||||
|
||||
func setMarkedText(_ string: Any, selectedRange: NSRange, replacementRange: NSRange) {
|
||||
@ -954,7 +961,33 @@ extension Ghostty.SurfaceView: NSTextInputClient {
|
||||
}
|
||||
|
||||
func attributedSubstring(forProposedRange range: NSRange, actualRange: NSRangePointer?) -> NSAttributedString? {
|
||||
return nil
|
||||
// We ignore the proposed range and always return the selection from
|
||||
// this (if we have one). This enables features like QuickLook. I don't
|
||||
// know if this breaks anything else...
|
||||
guard let surface = self.surface else { return nil }
|
||||
guard ghostty_surface_has_selection(surface) else { return nil }
|
||||
|
||||
// Get our selection. We cap it at 1MB for the purpose of this. This is
|
||||
// arbitrary. If this is a good reason to increase it I'm happy to.
|
||||
let v = String(unsafeUninitializedCapacity: 1000000) {
|
||||
Int(ghostty_surface_selection(surface, $0.baseAddress, UInt($0.count)))
|
||||
}
|
||||
|
||||
// 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()
|
||||
}
|
||||
|
||||
return .init(string: v, attributes: attributes)
|
||||
}
|
||||
|
||||
func characterIndex(for point: NSPoint) -> Int {
|
||||
|
@ -10,6 +10,7 @@ const assert = std.debug.assert;
|
||||
const Allocator = std.mem.Allocator;
|
||||
const objc = @import("objc");
|
||||
const apprt = @import("../apprt.zig");
|
||||
const font = @import("../font/main.zig");
|
||||
const input = @import("../input.zig");
|
||||
const renderer = @import("../renderer.zig");
|
||||
const terminal = @import("../terminal/main.zig");
|
||||
@ -1771,6 +1772,37 @@ pub const CAPI = struct {
|
||||
surface.renderer_thread.wakeup.notify() catch {};
|
||||
}
|
||||
|
||||
/// This returns a CTFontRef that should be used for quicklook
|
||||
/// highlighted text. This is always the primary font in use
|
||||
/// regardless of the selected text. If coretext is not in use
|
||||
/// then this will return nothing.
|
||||
export fn ghostty_surface_quicklook_font(ptr: *Surface) ?*anyopaque {
|
||||
// For non-CoreText we just return null.
|
||||
if (comptime font.options.backend != .coretext) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Get the shared font grid. We acquire a read lock to
|
||||
// read the font face. It should not be deffered since
|
||||
// we're loading the primary face.
|
||||
const grid = ptr.core_surface.renderer.font_grid;
|
||||
grid.lock.lockShared();
|
||||
defer grid.lock.unlockShared();
|
||||
|
||||
const collection = &grid.resolver.collection;
|
||||
const face = collection.getFace(.{}) catch return null;
|
||||
|
||||
// The font is not the right size by default so we need
|
||||
// to set it to our configured window size.
|
||||
const copy = face.font.copyWithAttributes(
|
||||
ptr.app.config.@"font-size",
|
||||
null,
|
||||
null,
|
||||
) catch return null;
|
||||
|
||||
return copy;
|
||||
}
|
||||
|
||||
export fn ghostty_inspector_metal_init(ptr: *Inspector, device: objc.c.id) bool {
|
||||
return ptr.initMetal(objc.Object.fromId(device));
|
||||
}
|
||||
|
Reference in New Issue
Block a user