Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse code

Add preliminary bundle install support.

  • Loading branch information...
commit df853493a94c9e7ffaab72ee0e681f3688b3b2a3 1 parent 4121353
Allan Odgaard sorbits authored
12 Applications/TextMate/src/AppController.mm
... ... @@ -1,6 +1,7 @@
1 1 #import "AppController.h"
2 2 #import "Favorites.h"
3 3 #import "CreditsWindowController.h"
  4 +#import "InstallBundleItems.h"
4 5 #import <oak/oak.h>
5 6 #import <oak/debug.h>
6 7 #import <Find/Find.h>
@@ -23,9 +24,15 @@
23 24 void OakOpenDocuments (NSArray* paths)
24 25 {
25 26 std::vector<document::document_ptr> documents;
  27 + NSMutableArray* itemsToInstall = [NSMutableArray array];
26 28 for(NSString* path in paths)
27 29 {
28   - if(path::is_directory(to_s(path)))
  30 + static std::set<std::string> const tmItemExtensions = { "tmbundle", "tmcommand", "tmdragcommand", "tmlanguage", "tmmacro", "tmplugin", "tmpreferences", "tmsnippet", "tmtheme" };
  31 + if(tmItemExtensions.find(to_s([[path pathExtension] lowercaseString])) != tmItemExtensions.end() && !([NSEvent modifierFlags] & NSAlternateKeyMask))
  32 + {
  33 + [itemsToInstall addObject:path];
  34 + }
  35 + else if(path::is_directory(to_s(path)))
29 36 {
30 37 document::show_browser(to_s(path));
31 38 }
@@ -35,6 +42,9 @@ void OakOpenDocuments (NSArray* paths)
35 42 }
36 43 }
37 44
  45 + if([itemsToInstall count])
  46 + InstallBundleItems(itemsToInstall);
  47 +
38 48 document::show(documents);
39 49 }
40 50
3  Applications/TextMate/src/InstallBundleItems.h
... ... @@ -0,0 +1,3 @@
  1 +#import <oak/misc.h>
  2 +
  3 +PUBLIC void InstallBundleItems (NSArray* itemPaths);
210 Applications/TextMate/src/InstallBundleItems.mm
... ... @@ -0,0 +1,210 @@
  1 +#import "InstallBundleItems.h"
  2 +#import <BundleEditor/BundleEditor.h>
  3 +#import <OakFoundation/NSString Additions.h>
  4 +#import <bundles/bundles.h>
  5 +#import <text/ctype.h>
  6 +#import <regexp/format_string.h>
  7 +#import <io/io.h>
  8 +#import <ns/ns.h>
  9 +
  10 +static std::map<std::string, bundles::item_ptr> installed_items ()
  11 +{
  12 + std::map<std::string, bundles::item_ptr> res;
  13 + citerate(item, bundles::query(bundles::kFieldAny, NULL_STR, scope::wildcard, bundles::kItemTypeAny, oak::uuid_t(), false, true))
  14 + {
  15 + citerate(path, (*item)->paths())
  16 + res.insert(std::make_pair(*path, *item));
  17 + }
  18 + return res;
  19 +}
  20 +
  21 +void InstallBundleItems (NSArray* itemPaths)
  22 +{
  23 + struct info_t
  24 + {
  25 + info_t (std::string const& path, std::string const& name, oak::uuid_t const& uuid, bool isBundle, bundles::item_ptr installed = bundles::item_ptr()) : path(path), name(name), uuid(uuid), is_bundle(isBundle), installed(installed) { }
  26 +
  27 + std::string path;
  28 + std::string name;
  29 + oak::uuid_t uuid;
  30 + bool is_bundle;
  31 + bundles::item_ptr installed;
  32 + };
  33 +
  34 + std::map<std::string, bundles::item_ptr> const installedItems = installed_items();
  35 + std::vector<info_t> installed, toInstall, delta, malformed;
  36 +
  37 + for(NSString* path in itemPaths)
  38 + {
  39 + bool isDelta;
  40 + std::string bundleName;
  41 + oak::uuid_t bundleUUID;
  42 + bundles::item_ptr installedItem;
  43 +
  44 + bool isBundle = [[[path pathExtension] lowercaseString] isEqualToString:@"tmbundle"];
  45 + std::string const loadPath = isBundle ? path::join(to_s(path), "info.plist") : to_s(path);
  46 + plist::dictionary_t const infoPlist = plist::load(loadPath);
  47 +
  48 + auto it = installedItems.find(loadPath);
  49 + if(it != installedItems.end())
  50 + installedItem = it->second;
  51 +
  52 + if(plist::get_key_path(infoPlist, "isDelta", isDelta) && isDelta)
  53 + {
  54 + delta.push_back(info_t(to_s(path), NULL_STR, oak::uuid_t(), isBundle, installedItem));
  55 + }
  56 + else if(plist::get_key_path(infoPlist, "name", bundleName) && plist::get_key_path(infoPlist, "uuid", bundleUUID))
  57 + {
  58 + if(installedItem)
  59 + installed.push_back(info_t(to_s(path), bundleName, bundleUUID, isBundle, installedItem));
  60 + else toInstall.push_back(info_t(to_s(path), bundleName, bundleUUID, isBundle, installedItem));
  61 + }
  62 + else
  63 + {
  64 + malformed.push_back(info_t(to_s(path), NULL_STR, oak::uuid_t(), isBundle, installedItem));
  65 + }
  66 + }
  67 +
  68 + iterate(info, delta)
  69 + {
  70 + char const* type = info->is_bundle ? "bundle" : "bundle item";
  71 + std::string const name = path::name(path::strip_extension(info->path));
  72 + std::string const title = text::format("The %s “%s” could not be installed because it is in delta format.", type, name.c_str());
  73 + NSRunAlertPanel([NSString stringWithCxxString:title], @"Contact the author of this %s to get a properly exported version.", @"OK", nil, nil, type);
  74 + }
  75 +
  76 + iterate(info, malformed)
  77 + {
  78 + char const* type = info->is_bundle ? "bundle" : "bundle item";
  79 + std::string const name = path::name(path::strip_extension(info->path));
  80 + std::string const title = text::format("The %s “%s” could not be installed because it is malformed.", type, name.c_str());
  81 + NSRunAlertPanel([NSString stringWithCxxString:title], @"The %s lacks mandatory keys in its property list file.", @"OK", nil, nil, type);
  82 + }
  83 +
  84 + iterate(info, installed)
  85 + {
  86 + char const* type = info->is_bundle ? "bundle" : "bundle item";
  87 + std::string const name = info->name;
  88 + std::string const title = text::format("The %s “%s” is already installed.", type, name.c_str());
  89 + int choice = NSRunAlertPanel([NSString stringWithCxxString:title], @"You can edit the installed %s to inspect it.", @"OK", @"Edit", nil, type);
  90 + if(choice == NSAlertAlternateReturn) // "Edit"
  91 + [[BundleEditor sharedInstance] revealBundleItem:info->installed];
  92 + }
  93 +
  94 + iterate(info, toInstall)
  95 + {
  96 + if(info->is_bundle)
  97 + {
  98 + int choice = NSRunAlertPanel([NSString stringWithFormat:@"Would you like to install the “%@” bundle?", [NSString stringWithCxxString:info->name]], @"Installing a bundle adds new functionality to TextMate.", @"Install", @"Cancel", nil);
  99 + if(choice == NSAlertDefaultReturn) // "Install"
  100 + {
  101 + std::string const installDir = path::join(path::home(), "Library/Application Support/Avian/Pristine Copy/Bundles");
  102 + if(path::make_dir(installDir))
  103 + {
  104 + std::string const installPath = path::unique(path::join(installDir, path::name(info->path)));
  105 + if(path::copy(info->path, installPath))
  106 + {
  107 + fprintf(stderr, "installed bundle at: %s\n", installPath.c_str());
  108 + continue;
  109 + }
  110 + }
  111 + fprintf(stderr, "failed to install bundle: %s\n", info->path.c_str());
  112 + }
  113 + }
  114 + else
  115 + {
  116 + oak::uuid_t defaultBundle;
  117 + if(path::extension(info->path) == ".tmTheme")
  118 + {
  119 + citerate(item, bundles::query(bundles::kFieldAny, NULL_STR, scope::wildcard, bundles::kItemTypeBundle, "A4380B27-F366-4C70-A542-B00D26ED997E"))
  120 + defaultBundle = (*item)->uuid();
  121 + }
  122 +
  123 + std::map<std::string, std::string> vars;
  124 + vars.insert(std::make_pair("TM_FULLNAME", getpwuid(getuid())->pw_gecos ?: "John Doe"));
  125 + std::string personalBundleName = format_string::expand("${TM_FULLNAME/^(\\S+).*$/$1/}’s Bundle", vars);
  126 + // std::string personalBundleName = format_string::expand("${TM_FULLNAME/^(\\S+).*$/$1/}’s Bundle", std::map<std::string, std::string>{ { "TM_FULLNAME", getpwuid(getuid())->pw_gecos ?: "John Doe" } });
  127 + if(!defaultBundle)
  128 + {
  129 + citerate(item, bundles::query(bundles::kFieldName, personalBundleName, scope::wildcard, bundles::kItemTypeBundle))
  130 + defaultBundle = (*item)->uuid();
  131 + }
  132 +
  133 + NSPopUpButton* bundleChooser = [[[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO] autorelease];
  134 + [bundleChooser.menu removeAllItems];
  135 + [bundleChooser.menu addItemWithTitle:@"Create new bundle…" action:NULL keyEquivalent:@""];
  136 + [bundleChooser.menu addItem:[NSMenuItem separatorItem]];
  137 +
  138 + std::multimap<std::string, bundles::item_ptr, text::less_t> ordered;
  139 + citerate(item, bundles::query(bundles::kFieldAny, NULL_STR, scope::wildcard, bundles::kItemTypeBundle))
  140 + ordered.insert(std::make_pair((*item)->name(), *item));
  141 + NSMenuItem* selectedItem = nil;
  142 + iterate(pair, ordered)
  143 + {
  144 + NSMenuItem* menuItem = [bundleChooser.menu addItemWithTitle:[NSString stringWithCxxString:pair->first] action:NULL keyEquivalent:@""];
  145 + [menuItem setRepresentedObject:[NSString stringWithCxxString:to_s(pair->second->uuid())]];
  146 + if(defaultBundle && defaultBundle == pair->second->uuid())
  147 + selectedItem = menuItem;
  148 + }
  149 + if(selectedItem)
  150 + [bundleChooser selectItem:selectedItem];
  151 +
  152 + [bundleChooser sizeToFit];
  153 + NSRect frame = [bundleChooser frame];
  154 + if(NSWidth(frame) > 200)
  155 + [bundleChooser setFrameSize:NSMakeSize(200, NSHeight(frame))];
  156 +
  157 + NSAlert* alert = [NSAlert alertWithMessageText:[NSString stringWithFormat:@"Would you like to install the “%@” bundle item?", [NSString stringWithCxxString:info->name]] defaultButton:@"Install" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Installing a bundle item adds new functionality to TextMate."];
  158 + [alert setAccessoryView:bundleChooser];
  159 + if([alert runModal] == NSAlertDefaultReturn) // "Install"
  160 + {
  161 + static struct { std::string extension; std::string directory; } DirectoryMap[] =
  162 + {
  163 + { ".tmCommand", "Commands" },
  164 + { ".tmDragCommand", "DragCommands" },
  165 + { ".tmMacro", "Macros" },
  166 + { ".tmPreferences", "Preferences" },
  167 + { ".tmSnippet", "Snippets" },
  168 + { ".tmLanguage", "Syntaxes" },
  169 + { ".tmProxy", "Proxies" },
  170 + { ".tmTheme", "Themes" },
  171 + };
  172 +
  173 + if(NSString* bundleUUID = [[bundleChooser selectedItem] representedObject])
  174 + {
  175 + citerate(item, bundles::query(bundles::kFieldAny, NULL_STR, scope::wildcard, bundles::kItemTypeBundle, to_s(bundleUUID)))
  176 + {
  177 + if((*item)->local() || (*item)->save())
  178 + {
  179 + std::string dest = path::parent((*item)->paths().front());
  180 + iterate(iter, DirectoryMap)
  181 + {
  182 + if(path::extension(info->path) == iter->extension)
  183 + {
  184 + dest = path::join(dest, iter->directory);
  185 + if(path::make_dir(dest))
  186 + {
  187 + dest = path::join(dest, path::name(info->path));
  188 + dest = path::unique(dest);
  189 + if(path::copy(info->path, dest))
  190 + break;
  191 + else fprintf(stderr, "error: copy(‘%s’, ‘%s’)\n", info->path.c_str(), dest.c_str());
  192 + }
  193 + else
  194 + {
  195 + fprintf(stderr, "error: makedir(‘%s’)\n", dest.c_str());
  196 + }
  197 + }
  198 + }
  199 +
  200 + }
  201 + }
  202 + }
  203 + else
  204 + {
  205 + fprintf(stderr, "Create new bundle for item\n");
  206 + }
  207 + }
  208 + }
  209 + }
  210 +}

0 comments on commit df85349

Please sign in to comment.
Something went wrong with that request. Please try again.