From 8ef7eba3bd68f3c6db82090b9c6b5662f67d8966 Mon Sep 17 00:00:00 2001 From: Dzmitry Neviadomski Date: Fri, 13 Oct 2023 02:53:57 +0300 Subject: [PATCH] feat: render file tree in legacy html-based QuickLook preview extension (#6091) Signed-off-by: Dzmitry Neviadomski --- .../QuickLookPlugin/GeneratePreviewForURL.mm | 91 ++++++++++++++++--- 1 file changed, 79 insertions(+), 12 deletions(-) diff --git a/macosx/QuickLookPlugin/GeneratePreviewForURL.mm b/macosx/QuickLookPlugin/GeneratePreviewForURL.mm index 7c3c8c868..431202e1e 100644 --- a/macosx/QuickLookPlugin/GeneratePreviewForURL.mm +++ b/macosx/QuickLookPlugin/GeneratePreviewForURL.mm @@ -3,8 +3,11 @@ @import QuickLook; #include +#include +#include #include +#include #import "NSStringAdditions.h" @@ -13,6 +16,31 @@ OSStatus GeneratePreviewForURL(void* thisInterface, QLPreviewRequestRef preview, void CancelPreviewGeneration(void* thisInterface, QLPreviewRequestRef preview); QL_EXTERN_C_END +static NSUInteger const kIconWidth = 16; + +namespace +{ + +class FileTreeNode +{ + public: + FileTreeNode() = default; + ~FileTreeNode() = default; + + auto MaybeCreateChild(std::string_view child_name) + { + return children_.try_emplace(std::string{ child_name }); + } + + private: + FileTreeNode(FileTreeNode const&) = delete; + FileTreeNode& operator=(FileTreeNode&) = delete; + + std::unordered_map children_; +}; + +} // namespace + NSString* generateIconData(NSString* fileExtension, NSUInteger width, NSMutableDictionary* allImgProps) { NSString* rawFilename = ![fileExtension isEqualToString:@""] ? fileExtension : @"blank_file_name_transmission"; @@ -189,22 +217,61 @@ OSStatus GeneratePreviewForURL(void* /*thisInterface*/, QLPreviewRequestRef prev localizedStringWithFormat:NSLocalizedStringFromTableInBundle(@"%lu Files", nil, bundle, "quicklook file header"), n_files]; [listSection appendFormat:@"%@", fileTitleString]; -#warning display folders? + FileTreeNode root{}; + for (auto const& [path, size] : metainfo.files().sortedByPath()) { - NSString* fullFilePath = @(path.c_str()); - NSCAssert([fullFilePath hasPrefix:[name stringByAppendingString:@"/"]], @"Expected file path %@ to begin with %@/", fullFilePath, name); + FileTreeNode* curNode = &root; + size_t level = 0; - NSString* shortenedFilePath = [fullFilePath substringFromIndex:name.length + 1]; - NSString* fileSize = [NSString stringForFileSize:size]; + auto subpath = std::string_view{ path }; + auto path_vec = std::vector{}; + auto token = std::string_view{}; + while (tr_strv_sep(&subpath, &token, '/')) + { + path_vec.emplace_back(token); + } + size_t const last = path_vec.size() - 1; - NSUInteger const icon_width = 16; - [listSection appendFormat:@"%@%@", - generateIconData(shortenedFilePath.pathExtension, icon_width, allImgProps), - icon_width, - icon_width, - shortenedFilePath, - fileSize]; + for (auto const& part : path_vec) + { + auto [it, inserted] = curNode->MaybeCreateChild(part); + if (inserted) + { + NSString* prefix = @""; + for (size_t i = 0; i < level; ++i) + { + prefix = [prefix stringByAppendingString:@" "]; + } + + NSString* pathPart = @(it->first.c_str()); + NSString* pathExt = nil; + NSString* fileSize = nil; + if (level < last) + { + // This node is a directory. + pathExt = NSFileTypeForHFSTypeCode(kGenericFolderIcon); + fileSize = @""; + } + else + { + // This node is a leaf file. + pathExt = pathPart.pathExtension; + fileSize = [NSString stringForFileSize:size]; + } + + [listSection appendFormat:@"%@%@%@", + prefix, + generateIconData(pathExt, kIconWidth, allImgProps), + kIconWidth, + kIconWidth, + pathPart, + fileSize]; + } + + curNode = &it->second; + level++; + } } [listSection appendString:@""];