macos: hacky API to get a CTFont for QuickLook

This commit is contained in:
Mitchell Hashimoto
2024-06-18 17:43:39 -04:00
parent 280b8efacc
commit d5f27245d4
3 changed files with 68 additions and 2 deletions

View File

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

View File

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

View File

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