mirror of
https://github.com/textmate/textmate.git
synced 2026-02-14 08:24:56 -05:00
734 lines
27 KiB
Plaintext
734 lines
27 KiB
Plaintext
#import "BundleEditor.h"
|
||
#import "PropertiesViewController.h"
|
||
#import "OakRot13Transformer.h"
|
||
#import "be_entry.h"
|
||
#import <OakFoundation/OakFoundation.h>
|
||
#import <OakFoundation/NSString Additions.h>
|
||
#import <OakFoundation/OakStringListTransformer.h>
|
||
#import <OakAppKit/NSAlert Additions.h>
|
||
#import <OakAppKit/NSImage Additions.h>
|
||
#import <OakAppKit/OakSound.h>
|
||
#import <OakAppKit/OakFileIconImage.h>
|
||
#import <OakTextView/OakDocumentView.h>
|
||
#import <plist/ascii.h>
|
||
#import <plist/delta.h>
|
||
#import <text/decode.h>
|
||
#import <cf/cf.h>
|
||
#import <ns/ns.h>
|
||
#import <oak/debug.h>
|
||
#import <oak/CocoaSTL.h>
|
||
|
||
OAK_DEBUG_VAR(BundleEditor);
|
||
|
||
static BundleEditor* SharedInstance;
|
||
|
||
@interface BundleEditor ()
|
||
- (void)didChangeBundleItems;
|
||
- (void)didChangeModifiedState;
|
||
@property (nonatomic, retain) PropertiesViewController* sharedPropertiesViewController;
|
||
@property (nonatomic, retain) PropertiesViewController* extraPropertiesViewController;
|
||
@property (nonatomic, retain) NSMutableDictionary* bundleItemProperties;
|
||
- (bundles::item_ptr const&)bundleItem;
|
||
- (void)setBundleItem:(bundles::item_ptr const&)aBundleItem;
|
||
@end
|
||
|
||
@interface NSBrowser (SnowLeopard)
|
||
- (void)setAutohidesScroller:(BOOL)flag;
|
||
@end
|
||
|
||
namespace
|
||
{
|
||
static bundles::kind_t const PlistItemKinds[] = { bundles::kItemTypeSettings, bundles::kItemTypeMacro, bundles::kItemTypeTheme };
|
||
|
||
static struct item_info_t { bundles::kind_t kind; std::string plist_key; std::string grammar; std::string file_type; std::string kind_string; NSString* view_controller; NSString* file; } item_infos[] =
|
||
{
|
||
{ bundles::kItemTypeBundle, "description", "text.html.basic", "tmBundle", "bundle", @"BundleProperties", @"Bundle" },
|
||
{ bundles::kItemTypeCommand, "command", NULL_STR, "tmCommand", "command", @"CommandProperties", @"Command" },
|
||
{ bundles::kItemTypeDragCommand, "command", NULL_STR, "tmDragCommand", "dragCommand", @"FileDropProperties", @"Drag Command" },
|
||
{ bundles::kItemTypeSnippet, "content", "text.tm-snippet", "tmSnippet", "snippet", @"SnippetProperties", @"Snippet" },
|
||
{ bundles::kItemTypeSettings, "settings", "source.plist.textmate.settings", "tmPreferences", "settings", nil, @"Settings" },
|
||
{ bundles::kItemTypeGrammar, NULL_STR, "source.plist.textmate.grammar", "tmLanguage", "grammar", @"GrammarProperties", @"Grammar" },
|
||
{ bundles::kItemTypeProxy, "content", "text.plain", "tmProxy", "proxy", nil, @"Proxy" },
|
||
{ bundles::kItemTypeTheme, "settings", "meta.plist", "tmTheme", "theme", @"ThemeProperties", @"Theme" },
|
||
{ bundles::kItemTypeMacro, "commands", "meta.plist", "tmMacro", "macro", @"MacroProperties", @"Macro" },
|
||
};
|
||
|
||
item_info_t const& info_for (bundles::kind_t kind)
|
||
{
|
||
iterate(it, item_infos)
|
||
{
|
||
if(it->kind == kind)
|
||
return *it;
|
||
}
|
||
|
||
static item_info_t dummy;
|
||
return dummy;
|
||
}
|
||
}
|
||
|
||
static NSMutableArray* wrap_array (std::vector<std::string> const& array, NSString* key)
|
||
{
|
||
NSMutableArray* res = [NSMutableArray array];
|
||
iterate(str, array)
|
||
[res addObject:[NSMutableDictionary dictionaryWithObject:[NSString stringWithCxxString:*str] forKey:key]];
|
||
return res;
|
||
}
|
||
|
||
static plist::array_t unwrap_array (NSArray* array, NSString* key)
|
||
{
|
||
plist::array_t res;
|
||
iterate(dict, array)
|
||
res.push_back(to_s((NSString*)[*dict objectForKey:key]));
|
||
return res;
|
||
}
|
||
|
||
namespace
|
||
{
|
||
struct expand_visitor_t : boost::static_visitor<void>
|
||
{
|
||
expand_visitor_t (std::map<std::string, std::string> const& variables) : _variables(variables) { }
|
||
|
||
template <typename T>
|
||
void operator() (T value) const { }
|
||
void operator() (std::string& str) const { str = format_string::expand(str, _variables); }
|
||
void operator() (plist::array_t& array) const { iterate(it, array) boost::apply_visitor(*this, *it); }
|
||
void operator() (plist::dictionary_t& dict) const { iterate(pair, dict) boost::apply_visitor(*this, pair->second); }
|
||
|
||
private:
|
||
std::map<std::string, std::string> const& _variables;
|
||
};
|
||
}
|
||
|
||
static be::entry_ptr parent_for_column (NSBrowser* aBrowser, NSInteger aColumn, be::entry_ptr entry)
|
||
{
|
||
for(size_t col = 0; col < aColumn; ++col)
|
||
{
|
||
NSInteger row = [aBrowser selectedRowInColumn:col];
|
||
if(row == -1)
|
||
{
|
||
fprintf(stderr, "*** abort\n");
|
||
return be::entry_ptr();
|
||
}
|
||
entry = entry->children()[row];
|
||
}
|
||
return entry;
|
||
}
|
||
|
||
@implementation BundleEditor
|
||
@synthesize sharedPropertiesViewController, extraPropertiesViewController, bundleItemProperties;
|
||
|
||
+ (BundleEditor*)sharedInstance
|
||
{
|
||
return SharedInstance ?: [[self new] autorelease];
|
||
}
|
||
|
||
- (id)init
|
||
{
|
||
if(SharedInstance)
|
||
{
|
||
[self release];
|
||
}
|
||
else if(self = SharedInstance = [[super initWithWindowNibName:@"BundleEditor"] retain])
|
||
{
|
||
D(DBF_BundleEditor, bug("\n"););
|
||
|
||
struct callback_t : bundles::callback_t
|
||
{
|
||
callback_t (BundleEditor* self) : self(self) { }
|
||
void bundles_did_change () { [self didChangeBundleItems]; }
|
||
private:
|
||
BundleEditor* self;
|
||
};
|
||
|
||
static callback_t cb(self);
|
||
bundles::add_callback(&cb);
|
||
|
||
struct document_callback_t : document::document_t::callback_t
|
||
{
|
||
document_callback_t (BundleEditor* self) : _self(self) { }
|
||
|
||
void handle_document_event (document::document_ptr document, event_t event)
|
||
{
|
||
switch(event)
|
||
{
|
||
case did_change_modified_status:
|
||
[_self didChangeModifiedState];
|
||
break;
|
||
}
|
||
}
|
||
|
||
private:
|
||
BundleEditor* _self;
|
||
};
|
||
|
||
documentCallback = new document_callback_t(self);
|
||
}
|
||
return SharedInstance;
|
||
}
|
||
|
||
- (void)dealloc
|
||
{
|
||
if(bundleItemContent)
|
||
bundleItemContent->remove_callback(documentCallback);
|
||
delete documentCallback;
|
||
|
||
[sharedPropertiesViewController release];
|
||
[extraPropertiesViewController release];
|
||
self.bundleItemProperties = nil;
|
||
[drawer release];
|
||
[super dealloc];
|
||
}
|
||
|
||
- (void)windowDidLoad
|
||
{
|
||
static struct { NSString* name; NSArray* array; } const converters[] =
|
||
{
|
||
{ @"OakSaveStringListTransformer", @[ @"nop", @"saveActiveFile", @"saveModifiedFiles" ] },
|
||
{ @"OakInputStringListTransformer", @[ @"selection", @"document", @"scope", @"line", @"word", @"character", @"none" ] },
|
||
{ @"OakInputFormatStringListTransformer", @[ @"text", @"xml" ] },
|
||
{ @"OakOutputLocationStringListTransformer", @[ @"replaceInput", @"replaceDocument", @"atCaret", @"afterInput", @"newWindow", @"toolTip", @"discard", @"replaceSelection" ] },
|
||
{ @"OakOutputFormatStringListTransformer", @[ @"text", @"snippet", @"html", @"completionList" ] },
|
||
{ @"OakOutputCaretStringListTransformer", @[ @"afterOutput", @"selectOutput", @"interpolateByChar", @"interpolateByLine", @"heuristic" ] },
|
||
};
|
||
|
||
[OakRot13Transformer register];
|
||
for(size_t i = 0; i != sizeofA(converters); ++i)
|
||
[OakStringListTransformer createTransformerWithName:converters[i].name andObjectsArray:converters[i].array];
|
||
|
||
drawer = [[NSDrawer alloc] initWithContentSize:NSZeroSize preferredEdge:NSMaxXEdge];
|
||
[drawer setParentWindow:[self window]];
|
||
|
||
bundles = be::bundle_entries();
|
||
|
||
[browser setDelegate:self];
|
||
[browser loadColumnZero];
|
||
if([browser respondsToSelector:@selector(setAutohidesScroller:)])
|
||
[browser setAutohidesScroller:YES];
|
||
|
||
[[self window] makeFirstResponder:browser];
|
||
}
|
||
|
||
- (void)didChangeBundleItems
|
||
{
|
||
std::vector<std::string> selection;
|
||
be::entry_ptr entry = bundles;
|
||
for(NSInteger col = 0; col < [browser lastColumn]+1; ++col)
|
||
{
|
||
NSInteger row = [browser selectedRowInColumn:col];
|
||
if(row == -1)
|
||
break;
|
||
entry = entry->children()[row];
|
||
selection.push_back(entry->identifier());
|
||
}
|
||
|
||
bundles = be::bundle_entries();
|
||
[browser loadColumnZero];
|
||
|
||
entry = bundles;
|
||
for(size_t col = 0; col < selection.size(); ++col)
|
||
{
|
||
for(size_t row = 0; row < entry->children().size(); ++row)
|
||
{
|
||
if(selection[col] == entry->children()[row]->identifier())
|
||
{
|
||
[browser selectRow:row inColumn:col];
|
||
entry = entry->children()[row];
|
||
break;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
- (void)didChangeModifiedState
|
||
{
|
||
[self setDocumentEdited:bundleItem && (changes.find(bundleItem) != changes.end() || propertiesChanged || bundleItemContent->is_modified())];
|
||
}
|
||
|
||
// ==================
|
||
// = Action Methods =
|
||
// ==================
|
||
|
||
- (void)createItemOfType:(bundles::kind_t)aType
|
||
{
|
||
NSString* path = [[NSBundle bundleForClass:[self class]] pathForResource:info_for(aType).file ofType:@"plist"];
|
||
if(!path || ![[NSFileManager defaultManager] fileExistsAtPath:path])
|
||
return;
|
||
|
||
NSInteger row = [browser selectedRowInColumn:0];
|
||
bundles::item_ptr bundle = row != -1 ? bundles->children()[row]->represented_item() : bundles::item_ptr();
|
||
if(aType == bundles::kItemTypeBundle || bundle)
|
||
{
|
||
std::map<std::string, std::string> environment = variables_for_path();
|
||
ABMutableMultiValue* value = [[[ABAddressBook sharedAddressBook] me] valueForProperty:kABEmailProperty];
|
||
if(NSString* email = [value valueAtIndex:[value indexForIdentifier:[value primaryIdentifier]]])
|
||
environment.insert(std::make_pair("TM_ROT13_EMAIL", decode::rot13(to_s(email))));
|
||
|
||
bundles::item_ptr item(new bundles::item_t(oak::uuid_t().generate(), aType == bundles::kItemTypeBundle ? bundles::item_ptr() : bundle, aType));
|
||
plist::dictionary_t plist = plist::load(to_s(path));
|
||
expand_visitor_t visitor(environment);
|
||
visitor(plist);
|
||
plist[bundles::kFieldUUID] = to_s(item->uuid());
|
||
if(plist.find(bundles::kFieldName) == plist.end())
|
||
plist[bundles::kFieldName] = std::string("untitled");
|
||
item->set_plist(plist);
|
||
changes.insert(std::make_pair(item, plist));
|
||
bundles::add_item(item);
|
||
[self revealBundleItem:item];
|
||
[self didChangeModifiedState];
|
||
}
|
||
}
|
||
|
||
- (void)createItemSheetDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
|
||
{
|
||
if(returnCode == NSAlertDefaultReturn)
|
||
[self createItemOfType:(bundles::kind_t)[[(NSPopUpButton*)[alert accessoryView] selectedItem] tag]];
|
||
[alert release];
|
||
}
|
||
|
||
- (void)newDocument:(id)sender
|
||
{
|
||
// kItemTypeMacro, kItemTypeMenu, kItemTypeMenuItemSeparator
|
||
|
||
NSMenu* menu = [[[NSMenu alloc] initWithTitle:@"Item Types"] autorelease];
|
||
iterate(it, item_infos)
|
||
{
|
||
static int const types = bundles::kItemTypeBundle|bundles::kItemTypeCommand|bundles::kItemTypeDragCommand|bundles::kItemTypeSnippet|bundles::kItemTypeSettings|bundles::kItemTypeGrammar|bundles::kItemTypeProxy|bundles::kItemTypeTheme;
|
||
if((it->kind & types) == it->kind)
|
||
[[menu addItemWithTitle:it->file action:NULL keyEquivalent:@""] setTag:it->kind];
|
||
}
|
||
|
||
NSAlert* alert = [[NSAlert alertWithMessageText:@"Create New Item" defaultButton:@"Create" alternateButton:@"Cancel" otherButton:nil informativeTextWithFormat:@"Please choose what you want to create:"] retain];
|
||
NSPopUpButton* typeChooser = [[[NSPopUpButton alloc] initWithFrame:NSZeroRect pullsDown:NO] autorelease];
|
||
[typeChooser setMenu:menu];
|
||
[typeChooser sizeToFit];
|
||
[alert setAccessoryView:typeChooser];
|
||
[alert beginSheetModalForWindow:[self window] modalDelegate:self didEndSelector:@selector(createItemSheetDidEnd:returnCode:contextInfo:) contextInfo:NULL];
|
||
[[alert window] recalculateKeyViewLoop];
|
||
[[alert window] makeFirstResponder:typeChooser];
|
||
}
|
||
|
||
- (void)delete:(id)sender
|
||
{
|
||
if(bundleItem && bundleItem->move_to_trash())
|
||
{
|
||
OakPlayUISound(OakSoundDidTrashItemUISound);
|
||
bundles::item_ptr trashedItem = bundleItem;
|
||
|
||
bundles::item_ptr newSelectedItem;
|
||
bool foundItem = false;
|
||
iterate(entry, parent_for_column(browser, [browser selectedColumn], bundles)->children())
|
||
{
|
||
if(bundles::item_ptr item = (*entry)->represented_item())
|
||
{
|
||
if(item->uuid() == bundleItem->uuid())
|
||
foundItem = true;
|
||
else if(item->kind() != bundles::kItemTypeMenu && item->kind() != bundles::kItemTypeMenuItemSeparator)
|
||
newSelectedItem = item;
|
||
|
||
if(foundItem && newSelectedItem)
|
||
break;
|
||
}
|
||
}
|
||
|
||
if(newSelectedItem)
|
||
[self revealBundleItem:newSelectedItem];
|
||
|
||
changes.erase(trashedItem);
|
||
bundles::remove_item(trashedItem);
|
||
[self didChangeModifiedState];
|
||
}
|
||
}
|
||
|
||
- (void)revealBundleItem:(bundles::item_ptr const&)anItem
|
||
{
|
||
[self showWindow:self];
|
||
[self setBundleItem:anItem];
|
||
|
||
std::vector<be::entry_ptr> const& allBundles = bundles->children();
|
||
iterate(bundle, allBundles)
|
||
{
|
||
if((anItem->bundle() ?: anItem) != (*bundle)->represented_item())
|
||
continue;
|
||
|
||
[browser selectRow:(bundle - allBundles.begin()) inColumn:0];
|
||
for(std::vector< std::pair<std::vector<be::entry_ptr>, int> > stack(1, std::make_pair((*bundle)->children(), -1)); !stack.empty(); stack.pop_back())
|
||
{
|
||
for(++stack.back().second; stack.back().second < stack.back().first.size(); ++stack.back().second)
|
||
{
|
||
be::entry_ptr entry = stack.back().first[stack.back().second];
|
||
if(entry->has_children())
|
||
{
|
||
stack.push_back(std::make_pair(entry->children(), -1));
|
||
}
|
||
else if(entry->represented_item() == anItem)
|
||
{
|
||
for(size_t j = 0; j < stack.size(); ++j)
|
||
[browser selectRow:stack[j].second inColumn:j+1];
|
||
return;
|
||
}
|
||
}
|
||
}
|
||
}
|
||
}
|
||
|
||
- (BOOL)commitEditing
|
||
{
|
||
if(!bundleItem)
|
||
return YES;
|
||
|
||
[sharedPropertiesViewController commitEditing];
|
||
[extraPropertiesViewController commitEditing];
|
||
|
||
if(!propertiesChanged && !bundleItemContent->is_modified())
|
||
return YES;
|
||
|
||
plist::dictionary_t plist = plist::convert((CFPropertyListRef)bundleItemProperties);
|
||
|
||
std::string const& content = bundleItemContent->buffer().substr(0, bundleItemContent->buffer().size());
|
||
item_info_t const& info = info_for(bundleItem->kind());
|
||
if(info.plist_key == NULL_STR)
|
||
{
|
||
plist::any_t any = plist::parse_ascii(content);
|
||
if(plist::dictionary_t const* grammarPlist = boost::get<plist::dictionary_t>(&any))
|
||
{
|
||
static std::string const keys[] = { "comment", "patterns", "repository", "injections" };
|
||
iterate(key, keys)
|
||
{
|
||
if(grammarPlist->find(*key) != grammarPlist->end())
|
||
plist[*key] = grammarPlist->find(*key)->second;
|
||
else plist.erase(*key);
|
||
}
|
||
}
|
||
}
|
||
else
|
||
{
|
||
if(oak::contains(beginof(PlistItemKinds), endof(PlistItemKinds), info.kind))
|
||
plist[info.plist_key] = plist::parse_ascii(content);
|
||
else plist[info.plist_key] = content;
|
||
}
|
||
|
||
switch(info.kind)
|
||
{
|
||
case bundles::kItemTypeGrammar:
|
||
plist[bundles::kFieldGrammarExtension] = unwrap_array([bundleItemProperties objectForKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]], @"extension");
|
||
break;
|
||
|
||
case bundles::kItemTypeDragCommand:
|
||
plist[bundles::kFieldDropExtension] = unwrap_array([bundleItemProperties objectForKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]], @"extension");;
|
||
break;
|
||
}
|
||
|
||
if(plist::equal(plist, bundleItem->plist()))
|
||
changes.erase(bundleItem);
|
||
else changes[bundleItem] = plist;
|
||
|
||
propertiesChanged = NO;
|
||
bundleItemContent->set_disk_revision(bundleItemContent->revision());
|
||
|
||
[self didChangeModifiedState];
|
||
return YES;
|
||
}
|
||
|
||
- (void)saveDocument:(id)sender
|
||
{
|
||
[self commitEditing];
|
||
iterate(pair, changes)
|
||
{
|
||
pair->first->set_plist(pair->second);
|
||
pair->first->save();
|
||
}
|
||
changes.clear();
|
||
[self didChangeModifiedState];
|
||
}
|
||
|
||
// =====================
|
||
// = NSBrowserDelegate =
|
||
// =====================
|
||
|
||
- (NSInteger)browser:(NSBrowser*)aBrowser numberOfRowsInColumn:(NSInteger)aColumn
|
||
{
|
||
be::entry_ptr entry = parent_for_column(aBrowser, aColumn, bundles);
|
||
return entry && entry->has_children() ? entry->children().size() : 0;
|
||
}
|
||
|
||
- (void)browser:(NSBrowser*)aBrowser willDisplayCell:(id)aCell atRow:(NSInteger)aRow column:(NSInteger)aColumn
|
||
{
|
||
if(NSBrowserCell* cell = [aCell isKindOfClass:[NSBrowserCell class]] ? aCell : nil)
|
||
{
|
||
be::entry_ptr entry = parent_for_column(aBrowser, aColumn, bundles)->children()[aRow];
|
||
[cell setStringValue:[NSString stringWithCxxString:entry->name()]];
|
||
[cell setLeaf:!entry->has_children()];
|
||
[cell setEnabled:!entry->disabled()];
|
||
[cell setLoaded:YES];
|
||
|
||
if(bundles::item_ptr item = entry->represented_item())
|
||
{
|
||
[cell setImage:[NSImage imageNamed:info_for(item->kind()).file inSameBundleAsClass:[self class]]];
|
||
}
|
||
else
|
||
{
|
||
std::string const& path = entry->represented_path();
|
||
if(path != NULL_STR)
|
||
[cell setImage:[OakFileIconImage fileIconImageWithPath:[NSString stringWithCxxString:path] size:NSMakeSize(16, 16)]];
|
||
}
|
||
}
|
||
}
|
||
|
||
// ====================
|
||
// = NSBrowser Target =
|
||
// ====================
|
||
|
||
- (IBAction)browserSelectionDidChange:(id)sender
|
||
{
|
||
NSInteger aColumn = [browser selectedColumn];
|
||
NSInteger aRow = aColumn != -1 ? [browser selectedRowInColumn:aColumn] : -1;
|
||
if(aColumn != -1 && aRow != -1)
|
||
{
|
||
if(bundles::item_ptr item = parent_for_column(browser, aColumn, bundles)->children()[aRow]->represented_item())
|
||
{
|
||
if(item->kind() != bundles::kItemTypeMenu && item->kind() != bundles::kItemTypeMenuItemSeparator)
|
||
[self setBundleItem:item];
|
||
}
|
||
}
|
||
}
|
||
|
||
// =======================
|
||
// = Setting Bundle Item =
|
||
// =======================
|
||
|
||
- (void)observeValueForKeyPath:(NSString*)aKeyPath ofObject:(id)anObject change:(NSDictionary*)someChange context:(void*)context
|
||
{
|
||
propertiesChanged = YES;
|
||
[self didChangeModifiedState];
|
||
}
|
||
|
||
- (void)setBundleItemProperties:(NSMutableDictionary*)someProperties
|
||
{
|
||
static std::string const BindingKeys[] = { bundles::kFieldIsDisabled, bundles::kFieldName, bundles::kFieldKeyEquivalent, bundles::kFieldTabTrigger, bundles::kFieldScopeSelector, bundles::kFieldSemanticClass, bundles::kFieldContentMatch, bundles::kFieldHideFromUser, bundles::kFieldDropExtension, bundles::kFieldGrammarExtension, bundles::kFieldGrammarFirstLineMatch, bundles::kFieldGrammarScope, bundles::kFieldGrammarInjectionSelector, "beforeRunningCommand", "input", "inputFormat", "outputLocation", "outputFormat", "outputCaret", "autoScrollOutput", "contactName", "contactEmailRot13", "description", "disableAutoIndent", "useGlobalClipboard", "author", "comment" };
|
||
|
||
NSMutableDictionary* oldProperties = bundleItemProperties;
|
||
bundleItemProperties = [someProperties retain];
|
||
iterate(str, BindingKeys)
|
||
{
|
||
NSString* key = [NSString stringWithCxxString:*str];
|
||
[oldProperties removeObserver:self forKeyPath:key];
|
||
[someProperties addObserver:self forKeyPath:key options:0 context:NULL];
|
||
}
|
||
[oldProperties release];
|
||
propertiesChanged = NO;
|
||
}
|
||
|
||
static NSMutableDictionary* DictionaryForBundleItem (bundles::item_ptr const& aBundleItem)
|
||
{
|
||
NSMutableDictionary* res = ns::to_mutable_dictionary(aBundleItem->plist());
|
||
switch(info_for(aBundleItem->kind()).kind)
|
||
{
|
||
case bundles::kItemTypeCommand:
|
||
{
|
||
bundle_command_t cmd = parse_command(aBundleItem);
|
||
|
||
struct { NSString* key; int index; NSArray* array; } const popups[] =
|
||
{
|
||
{ @"beforeRunningCommand", cmd.pre_exec, @[ @"nop", @"saveActiveFile", @"saveModifiedFiles" ] },
|
||
{ @"input", cmd.input, @[ @"selection", @"document", @"scope", @"line", @"word", @"character", @"none" ] },
|
||
{ @"inputFormat", cmd.input_format, @[ @"text", @"xml" ] },
|
||
{ @"outputLocation", cmd.output, @[ @"replaceInput", @"replaceDocument", @"atCaret", @"afterInput", @"newWindow", @"toolTip", @"discard", @"replaceSelection" ] },
|
||
{ @"outputFormat", cmd.output_format, @[ @"text", @"snippet", @"html", @"completionList" ] },
|
||
{ @"outputCaret", cmd.output_caret, @[ @"afterOutput", @"selectOutput", @"interpolateByChar", @"interpolateByLine", @"heuristic" ] },
|
||
};
|
||
|
||
[res removeObjectForKey:@"output"];
|
||
[res removeObjectForKey:@"dontFollowNewOutput"];
|
||
[res setObject:@2 forKey:@"version"];
|
||
if(cmd.auto_scroll_output)
|
||
[res setObject:YES_obj forKey:@"autoScrollOutput"];
|
||
for(size_t i = 0; i != sizeofA(popups); ++i)
|
||
[res setObject:[popups[i].array objectAtIndex:popups[i].index] forKey:popups[i].key];
|
||
}
|
||
break;
|
||
|
||
case bundles::kItemTypeGrammar:
|
||
{
|
||
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldGrammarExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]];
|
||
}
|
||
break;
|
||
|
||
case bundles::kItemTypeDragCommand:
|
||
{
|
||
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldDropExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]];
|
||
}
|
||
break;
|
||
}
|
||
return res;
|
||
}
|
||
|
||
static NSMutableDictionary* DictionaryForPropertyList (plist::dictionary_t const& plist, bundles::item_ptr const& aBundleItem)
|
||
{
|
||
NSMutableDictionary* res = ns::to_mutable_dictionary(plist);
|
||
switch(info_for(aBundleItem->kind()).kind)
|
||
{
|
||
case bundles::kItemTypeGrammar:
|
||
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldGrammarExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldGrammarExtension]];
|
||
break;
|
||
|
||
case bundles::kItemTypeDragCommand:
|
||
[res setObject:wrap_array(aBundleItem->values_for_field(bundles::kFieldDropExtension), @"extension") forKey:[NSString stringWithCxxString:bundles::kFieldDropExtension]];
|
||
break;
|
||
}
|
||
return res;
|
||
}
|
||
|
||
- (bundles::item_ptr const&)bundleItem
|
||
{
|
||
return bundleItem;
|
||
}
|
||
|
||
- (BOOL)window:(NSWindow*)aWindow shouldDragDocumentWithEvent:(NSEvent*)anEvent from:(NSPoint)dragImageLocation withPasteboard:(NSPasteboard*)aPasteboard { return NO; }
|
||
- (BOOL)window:(NSWindow*)aWindow shouldPopUpDocumentPathMenu:(NSMenu*)aMenu { return NO; }
|
||
|
||
- (void)setBundleItem:(bundles::item_ptr const&)aBundleItem
|
||
{
|
||
if(bundleItem == aBundleItem)
|
||
return;
|
||
|
||
[self commitEditing];
|
||
|
||
if(bundleItemContent)
|
||
bundleItemContent->remove_callback(documentCallback);
|
||
|
||
bundleItem = aBundleItem;
|
||
bundleItemContent = document::document_ptr();
|
||
|
||
std::map<bundles::item_ptr, plist::dictionary_t>::const_iterator it = changes.find(bundleItem);
|
||
self.bundleItemProperties = it != changes.end() ? DictionaryForPropertyList(it->second, bundleItem) : DictionaryForBundleItem(bundleItem);
|
||
|
||
item_info_t const& info = info_for(bundleItem->kind());
|
||
|
||
[[self window] setTitle:[NSString stringWithCxxString:bundleItem->full_name()]];
|
||
[[self window] setRepresentedFilename:NSHomeDirectory()];
|
||
[[[self window] standardWindowButton:NSWindowDocumentIconButton] setImage:[[NSWorkspace sharedWorkspace] iconForFileType:[NSString stringWithCxxString:info.file_type]]];
|
||
|
||
plist::dictionary_t const& plist = it != changes.end() ? it->second : bundleItem->plist();
|
||
if(info.plist_key == NULL_STR)
|
||
{
|
||
plist::dictionary_t grammarPlist;
|
||
static std::string const keys[] = { "comment", "patterns", "repository", "injections" };
|
||
iterate(key, keys)
|
||
{
|
||
if(plist.find(*key) != plist.end())
|
||
grammarPlist[*key] = plist.find(*key)->second;
|
||
}
|
||
bundleItemContent = document::from_content(to_s(grammarPlist, plist::kPreferSingleQuotedStrings), info.grammar);
|
||
}
|
||
else if(oak::contains(beginof(PlistItemKinds), endof(PlistItemKinds), info.kind))
|
||
{
|
||
if(plist.find(info.plist_key) != plist.end())
|
||
bundleItemContent = document::from_content(to_s(plist.find(info.plist_key)->second, plist::kPreferSingleQuotedStrings), info.grammar);
|
||
}
|
||
else
|
||
{
|
||
std::string str;
|
||
if(plist::get_key_path(plist, info.plist_key, str))
|
||
{
|
||
if(info.kind == bundles::kItemTypeCommand || info.kind == bundles::kItemTypeDragCommand)
|
||
command::fix_shebang(&str);
|
||
bundleItemContent = document::from_content(str, info.grammar);
|
||
}
|
||
}
|
||
|
||
bundleItemContent = bundleItemContent ?: document::from_content("");
|
||
bundleItemContent->add_callback(documentCallback);
|
||
[documentView setDocument:bundleItemContent];
|
||
|
||
self.sharedPropertiesViewController = [[[PropertiesViewController alloc] initWithName:@"SharedProperties"] autorelease];
|
||
self.extraPropertiesViewController = nil;
|
||
[sharedPropertiesViewController setProperties:bundleItemProperties];
|
||
|
||
if(info.kind == bundles::kItemTypeBundle)
|
||
self.sharedPropertiesViewController = nil;
|
||
|
||
NSView* sharedView = [sharedPropertiesViewController view];
|
||
if(info.view_controller)
|
||
{
|
||
self.extraPropertiesViewController = [[[PropertiesViewController alloc] initWithName:info.view_controller] autorelease];
|
||
[extraPropertiesViewController setProperties:bundleItemProperties];
|
||
NSView* extraView = [extraPropertiesViewController view];
|
||
|
||
CGFloat delta = extraPropertiesViewController.indent - sharedPropertiesViewController.indent;
|
||
if(delta > 0)
|
||
[sharedView setFrame:NSOffsetRect(sharedView.frame, delta, 0)];
|
||
else [extraView setFrame:NSOffsetRect(extraView.frame, -delta, 0)];
|
||
|
||
NSView* contentView = [[[NSView alloc] initWithFrame:NSMakeRect(0, 0, std::max(NSMaxX(sharedView.frame) + 1, NSMaxX(extraView.frame) + 1), NSHeight(sharedView.frame) + NSHeight(extraView.frame))] autorelease];
|
||
[sharedView setAutoresizingMask:NSViewMinXMargin|NSViewMinYMargin];
|
||
[extraView setAutoresizingMask:NSViewMinXMargin|NSViewMinYMargin];
|
||
|
||
[sharedView setFrame:NSOffsetRect(sharedView.frame, 0, NSHeight(extraView.frame))];
|
||
[contentView addSubview:sharedView];
|
||
[contentView addSubview:extraView];
|
||
|
||
[drawer setContentView:[[[NSView alloc] initWithFrame:NSZeroRect] autorelease]];
|
||
[drawer setContentSize:contentView.frame.size];
|
||
[drawer setContentView:contentView];
|
||
[drawer open:self];
|
||
}
|
||
else
|
||
{
|
||
[sharedView setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable];
|
||
[drawer setContentSize:sharedView.frame.size];
|
||
[drawer setContentView:sharedView];
|
||
[drawer open:self];
|
||
}
|
||
[[[drawer contentView] window] recalculateKeyViewLoop];
|
||
}
|
||
|
||
- (void)closeWarningDidEnd:(NSAlert*)alert returnCode:(NSInteger)returnCode contextInfo:(void*)info
|
||
{
|
||
if(returnCode != NSAlertSecondButtonReturn) // Not "Cancel"
|
||
{
|
||
if(returnCode == NSAlertFirstButtonReturn) // "Save"
|
||
[self saveDocument:self];
|
||
else if(returnCode == NSAlertThirdButtonReturn) // "Don’t Save"
|
||
changes.clear();
|
||
[self close];
|
||
}
|
||
[alert release];
|
||
}
|
||
|
||
static NSString* DescriptionForChanges (std::map<bundles::item_ptr, plist::dictionary_t> const& changes)
|
||
{
|
||
NSString* res = [NSString stringWithCxxString:text::format("Do you want to save the changes made to %zu items?", changes.size())];
|
||
if(changes.size() == 1)
|
||
{
|
||
bundles::item_ptr item = changes.begin()->first;
|
||
NSString* name = [NSString stringWithCxxString:item->name()];
|
||
if(item->kind() == bundles::kItemTypeBundle)
|
||
{
|
||
res = [NSString stringWithFormat:@"Do you want to save the changes made to the bundle named “%@”?", name];
|
||
}
|
||
else
|
||
{
|
||
NSString* bundleName = [NSString stringWithCxxString:item->bundle()->name()];
|
||
NSString* type = [info_for(item->kind()).file lowercaseString];
|
||
res = [NSString stringWithFormat:@"Do you want to save the changes made to the %@ item named “%@” in the “%@” bundle?", type, name, bundleName];
|
||
}
|
||
}
|
||
return res;
|
||
}
|
||
|
||
- (BOOL)windowShouldClose:(id)sender
|
||
{
|
||
[self commitEditing];
|
||
if(changes.empty())
|
||
return YES;
|
||
|
||
NSAlert* alert = [[NSAlert alloc] init];
|
||
[alert setAlertStyle:NSWarningAlertStyle];
|
||
[alert setMessageText:DescriptionForChanges(changes)];
|
||
[alert setInformativeText:@"Your changes will be lost if you don't save them."];
|
||
[alert addButtons:@"Save", @"Cancel", @"Don’t Save", nil];
|
||
[alert beginSheetModalForWindow:self.window modalDelegate:self didEndSelector:@selector(closeWarningDidEnd:returnCode:contextInfo:) contextInfo:NULL];
|
||
return NO;
|
||
}
|
||
@end
|