• R/O
  • HTTP
  • SSH
  • HTTPS

Commit

Tags
No Tags

Frequently used words (click to add to your profile)

javaandroidc++linuxc#objective-ccocoa誰得qtrubypythonwindowsphpgamebathyscapheguic翻訳omegattwitterframeworkbtronvb.net計画中(planning stage)testdomarduinodirectxpreviewerゲームエンジン

TextMate is a graphical text editor for OS X 10.7+


Commit MetaInfo

Revisiona261a4c942d7c1c8c55798ee1cac79f9ec0ee8a9 (tree)
Time2012-08-23 07:57:46
AuthorAllan Odgaard <git@abet...>
CommiterAllan Odgaard

Log Message

Add preliminary bundle install support.

Change Summary

Incremental Difference

--- a/Applications/TextMate/src/AppController.mm
+++ b/Applications/TextMate/src/AppController.mm
@@ -1,6 +1,7 @@
11 #import "AppController.h"
22 #import "Favorites.h"
33 #import "CreditsWindowController.h"
4+#import "InstallBundleItems.h"
45 #import <oak/CocoaSTL.h>
56 #import <oak/oak.h>
67 #import <oak/debug.h>
@@ -24,9 +25,15 @@ OAK_DEBUG_VAR(AppController);
2425 void OakOpenDocuments (NSArray* paths)
2526 {
2627 std::vector<document::document_ptr> documents;
28+ NSMutableArray* itemsToInstall = [NSMutableArray array];
2729 for(NSString* path in paths)
2830 {
29- if(path::is_directory(to_s(path)))
31+ static std::string const tmItemExtensions[] = { "tmbundle", "tmcommand", "tmdragcommand", "tmlanguage", "tmmacro", "tmplugin", "tmpreferences", "tmsnippet", "tmtheme" };
32+ if(oak::contains(beginof(tmItemExtensions), endof(tmItemExtensions), to_s([[path pathExtension] lowercaseString])) && !([NSEvent modifierFlags] & NSAlternateKeyMask))
33+ {
34+ [itemsToInstall addObject:path];
35+ }
36+ else if(path::is_directory(to_s(path)))
3037 {
3138 document::show_browser(to_s(path));
3239 }
@@ -36,6 +43,9 @@ void OakOpenDocuments (NSArray* paths)
3643 }
3744 }
3845
46+ if([itemsToInstall count])
47+ InstallBundleItems(itemsToInstall);
48+
3949 document::show(documents);
4050 }
4151
--- /dev/null
+++ b/Applications/TextMate/src/InstallBundleItems.h
@@ -0,0 +1,3 @@
1+#import <oak/misc.h>
2+
3+PUBLIC void InstallBundleItems (NSArray* itemPaths);
--- /dev/null
+++ b/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+}