Зачастую возникают ситуации, когда функционал используемой IDE хочется расширить. Везет, если разработчику предоставлены средства и документация для того, чтобы это сделать. К сожалению, в случае c Xcode это не так. Документирование возможностей остановилось на версии Xcode 3.0, так что никто не гарантирует, что в следующей версии написанный вами плагин заработает.
Примечание: за основу для написания данного топика был взят плагин ColorSense-for-Xcode.
Как я уже говорил, официально, Xcode не предоставляет публичного API для написания плагинов. При старте приложения, Xcode просматривает папку с плагинами (~/Library/Application Support/Developer/Shared/Xcode/Plug-ins) и загружает найденные (.xcplugin).
На самом деле, простенький плагин пишется за несколько часов, что вы и увидите далее.
Создаем новый Xcode проект
Плагин — всего навсего OS X бандл, создадим новый проект с типом 'bundle'.
При создании проекта, нужно убедиться, что ARC выключен, так как Xсode работает под управлением garbage collector, это же распространяется и на плагин.
Открываем таргет плагина и выставляем следующие настройки:
- XC4Compatible = YES
- XCPluginHasUI = NO
- XCGCReady = YES
- Principal class = {название главного класса плагина}
Конфигурируем Build Settings
Идем в build settings и выставляем следующие настройки:
- Installation Build Products Location = ${HOME}
- Installation Directory = /Library/Application Support/Developer/Shared/Xcode/Plug-ins
- Deployment Location = YES
- Wrapper extension = xcplugin
Также, нужно добавить несколько user-definded settings:
- GCC_ENABLE_OBJC_GC = supported
- GCC_MODEL_TUNING = G5
Мы указали, куда должен устанавливаться наш плагин после сборки. Важно: с отладкой плагинов все печально, и вам придется перезапускать Xcode после каждого билда.
Пишем плагин
Создадим новый класс и назовем его тем именем, что укзали в настройке Principal class. Когда Xcode загружает плагин, будет вызван метод + (void) pluginDidLoad: (NSBundle*) plugin, в котором можно произвести начальную настройку плагина (как правило, плагин — это синглтон).
+ (void) pluginDidLoad: (NSBundle*) plugin {
static id sharedPlugin = nil;
static dispatch_once_t once;
dispatch_once(&once, ^{
sharedPlugin = [[self alloc] init];
});
}
- (id)init {
if (self = [super init]) {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(applicationDidFinishLaunching:)
name:NSApplicationDidFinishLaunchingNotification
object:nil];
}
return self;
}
в обработчике applicationDidFinishLaunching: мы можем непосредственно исполнять логику плагина. В нашем случае, мы подпишемся на нотификации изменения положения курсора в редакторе, а также добавим новый пункт в меню Edit.
- (void)applicationDidFinishLaunching:(NSNotification*)notification {
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(selectionDidChange:)
name:NSTextViewDidChangeSelectionNotification
object:nil];
NSMenuItem* editMenuItem = [[NSApp mainMenu] itemWithTitle:@"Edit"];
if (editMenuItem) {
[[editMenuItem submenu] addItem:[NSMenuItem separatorItem]];
NSMenuItem* newMenuItem = [[NSMenuItem alloc] initWithTitle:@"Show autoresizing masks"
action:@selector(toggleMasks:)
keyEquivalent:@"m"];
[newMenuItem setTarget:self];
[newMenuItem setKeyEquivalentModifierMask:NSAlternateKeyMask];
[[editMenuItem submenu] addItem:newMenuItem];
[newMenuItem release];
}
}
Для примера, будем выводить по изменению позиции курсора строку:
- (void)selectionDidChange:(NSNotification*)notification {
if ([[notification object] isKindOfClass:[NSTextView class]]) {
NSTextView* textView = (NSTextView *)[notification object];
if (![[NSUserDefaults standardUserDefaults] boolForKey:kDLShowSizingsPreferencesKey]) {
return;
}
NSArray* selectedRanges = [textView selectedRanges];
if (selectedRanges.count >= 1) {
NSRange selectedRange = [[selectedRanges objectAtIndex:0] rangeValue];
NSString *text = textView.textStorage.string;
NSRange lineRange = [text lineRangeForRange:selectedRange];
NSString *line = [text substringWithRange:lineRange];
}
NSAlert *alert = [[[NSAlert alloc] init] autorelease];
[alert setMessageText:line];
[alert runModal];
}
Простейший, ничего полезного не делающий плагин готов!
Замечания
Как я уже говорил выше, отладка плагина возможно только при помощи перезапуска Xcode (когда я писал плагин, я везде расставлял NSAlert и выводил нужную информацию). Из-за того, что документации как таковой нет, чтобы найти нужный вид в иерархии, или узнать какие нотификации отсылются, необходимо выполнить тот же самый трюк: вывести информацию либо в лог, либо в алерт. Если плагин падает, то Xcode не запустится, а плагин нужно удалить из ‘~/Library/Application Support/Developer/Shared/Xcode/Plug-ins’.
Более функциональный пример
Предпосылкой для написания статьи стал написанный мной небольшой плагин, который отображает маски авторазмера для UIView:
Исходник на github.
Спасибо за внимание!
Автор: garnett