From 3f716c27ade4199583662617aa1eb13fffadccb7 Mon Sep 17 00:00:00 2001 From: Nyx0uf Date: Thu, 10 Jul 2014 13:57:52 +0200 Subject: [PATCH] QuickLook: Handle thumbnail generation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Read at most 1 KiB or 50 lines of a file and render the attributed string in a bitmap context. The theme is set to Mac Classic to ensure a white background for best look when the previews are used as icons in Finder. The user’s default fixed width font in size 4 is used. With this font size only around 7 lines are shown, so we need to make it smaller, but we probably want to scale down the result to allow fractional line heights. A lot of code is copy/pasted from the QuickLook preview generator. --- .../QuickLookGenerator/resources/Info.plist | 6 + .../QuickLookGenerator/src/generate.mm | 112 ++++++++++++++++-- Applications/QuickLookGenerator/target | 2 +- 3 files changed, 111 insertions(+), 9 deletions(-) diff --git a/Applications/QuickLookGenerator/resources/Info.plist b/Applications/QuickLookGenerator/resources/Info.plist index cbe5ca86..0b6f7885 100644 --- a/Applications/QuickLookGenerator/resources/Info.plist +++ b/Applications/QuickLookGenerator/resources/Info.plist @@ -47,5 +47,11 @@ + QLThumbnailMinimumSize + 32 + QLSupportsConcurrentRequests + + QLNeedsToBeRunInMainThread + diff --git a/Applications/QuickLookGenerator/src/generate.mm b/Applications/QuickLookGenerator/src/generate.mm index c89bed40..64a0094d 100644 --- a/Applications/QuickLookGenerator/src/generate.mm +++ b/Applications/QuickLookGenerator/src/generate.mm @@ -17,6 +17,8 @@ OAK_EXTERN_C_BEGIN +static std::string const kMacClassicThemeUUID = "71D40D9D-AE48-11D9-920A-000D93589AF6"; + static void initialize (CFBundleRef generatorBundle) { static dispatch_once_t onceToken; @@ -42,12 +44,113 @@ static void initialize (CFBundleRef generatorBundle) }); } +static std::string URLtoString (CFURLRef url) +{ + std::string filePath = NULL_STR; + if(CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)) + { + filePath = cf::to_s(path); + CFRelease(path); + } + return filePath; +} + // ========================= // = QLGenerator interface = // ========================= OSStatus TextMateQuickLookPlugIn_GenerateThumbnailForURL (void* instance, QLThumbnailRequestRef request, CFURLRef url, CFStringRef contentTypeUTI, CFDictionaryRef options, CGSize maxSize) { + initialize(QLThumbnailRequestGetGeneratorBundle(request)); + + // Load file + ng::buffer_t buffer; + std::string filePath = URLtoString(url); + std::string fileContents = file::read_utf8(filePath, nullptr, 1024); // 1Kb should be more than enough + + // Trim to 50 lines, since we don't need more + size_t maxLines = 50; + fileContents.erase(std::find_if(fileContents.begin(), fileContents.end(), [&maxLines](char ch) { return ch == '\n' && --maxLines == 0; }), fileContents.end()); + buffer.insert(0, fileContents); + + // Check if cancelled + if(QLThumbnailRequestIsCancelled(request)) + return noErr; + + // Apply appropriate grammar + std::string fileType = file::type(filePath, std::make_shared(fileContents.data(), fileContents.size(), false)); + if(fileType != NULL_STR) + { + for(auto item : bundles::query(bundles::kFieldGrammarScope, fileType, scope::wildcard, bundles::kItemTypeGrammar)) + { + buffer.set_grammar(item); + break; + } + } + + // Check if cancelled + if(QLThumbnailRequestIsCancelled(request)) + return noErr; + + // Apply appropriate theme + theme_ptr theme = parse_theme(bundles::lookup(kMacClassicThemeUUID)); + if(!theme) + return noErr; + + NSFont* font = [NSFont userFixedPitchFontOfSize:4]; + theme = theme->copy_with_font_name_and_size(to_s([font fontName]), [font pointSize]); + + // Check if cancelled + if(QLThumbnailRequestIsCancelled(request)) + return noErr; + + // Perform syntax highlighting + buffer.wait_for_repair(); + std::map scopes = buffer.scopes(0, buffer.size()); + + // Construct RTF output + NSMutableAttributedString* output = (__bridge_transfer NSMutableAttributedString*)CFAttributedStringCreateMutable(kCFAllocatorDefault, buffer.size()); + size_t from = 0; + for(auto pair = scopes.begin(); pair != scopes.end(); ) + { + styles_t styles = theme->styles_for_scope(pair->second); + + size_t to = ++pair != scopes.end() ? pair->first : buffer.size(); + + [output appendAttributedString:[[NSAttributedString alloc] initWithString:[NSString stringWithCxxString:buffer.substr(from, to)] attributes:@{ + NSForegroundColorAttributeName : [NSColor tmColorWithCGColor:styles.foreground()], + NSBackgroundColorAttributeName : [NSColor whiteColor], + NSFontAttributeName : font, + NSUnderlineStyleAttributeName : @(styles.underlined() ? NSUnderlineStyleSingle : NSUnderlineStyleNone), + }]]; + + from = to; + } + + // Check if cancelled + if(QLThumbnailRequestIsCancelled(request)) + return noErr; + + // w/e the 3rd parameter, the context will always be a bitmap context + CGContextRef bitmapContext = QLThumbnailRequestCreateContext(request, maxSize, true, NULL); + if(bitmapContext) + { + NSGraphicsContext* context = [NSGraphicsContext graphicsContextWithGraphicsPort:bitmapContext flipped:YES]; + if(context) + { + [NSGraphicsContext saveGraphicsState]; + [NSGraphicsContext setCurrentContext:context]; + CGContextSaveGState(bitmapContext); + CGContextTranslateCTM(bitmapContext, 0.0, maxSize.height); + CGContextScaleCTM(bitmapContext, 1.0, -1.0); + [output drawAtPoint:(CGPoint){0.0, 0.0}]; + CGContextRestoreGState(bitmapContext); + [NSGraphicsContext restoreGraphicsState]; + } + QLThumbnailRequestFlushContext(request, bitmapContext); + CGContextRelease(bitmapContext); + } + return noErr; } @@ -61,14 +164,7 @@ OSStatus TextMateQuickLookPlugIn_GeneratePreviewForURL (void* instance, QLPrevie // Load file ng::buffer_t buffer; - - std::string filePath = NULL_STR; - if(CFStringRef path = CFURLCopyFileSystemPath(url, kCFURLPOSIXPathStyle)) - { - filePath = cf::to_s(path); - CFRelease(path); - } - + std::string filePath = URLtoString(url); std::string fileContents = file::read_utf8(filePath, nullptr, 20480); buffer.insert(0, fileContents); diff --git a/Applications/QuickLookGenerator/target b/Applications/QuickLookGenerator/target index 9e3ddbc9..73be01b1 100644 --- a/Applications/QuickLookGenerator/target +++ b/Applications/QuickLookGenerator/target @@ -1,7 +1,7 @@ TARGET_NAME = TextMateQL SOURCES = src/*.{c,mm} CP_Resources = resources/* -FRAMEWORKS = CoreFoundation QuickLook +FRAMEWORKS = CoreFoundation QuickLook AppKit LINK += layout buffer bundles file cf ns OakAppKit OakFoundation LN_FLAGS -= -Wl,-dead_strip_dylibs