Статьи

Как создать расширение редактора исходного кода XCode

Конечный продукт
Что вы будете создавать

Xcode — это основная среда разработки (Integrated Development Environment), используемая тысячами и тысячами разработчиков каждый день. Это потрясающий инструмент, но иногда вы хотите настроить некоторые его функции и поведение, чтобы лучше соответствовать вашему рабочему процессу.

До Xcode 7 можно было внедрить код в Xcode во время выполнения для создания плагинов. Плагины можно отправлять и распространять через большое приложение под названием Alcatraz . Это больше невозможно в Xcode 8.

Xcode 8 проверяет каждую библиотеку и пакет, чтобы предотвратить запуск вредоносного кода без вашего разрешения. Когда Xcode запускается, ранее установленные плагины с Alcatraz больше не загружаются. Не все потеряно, однако, Apple также объявила на WWDC возможность разработки расширений редактора исходного кода Xcode, чтобы каждый мог расширить существующие функции редактирования исходного кода. Давайте посмотрим, чего мы можем достичь с помощью таких расширений.

Расширения редактора исходного кода Xcode 8 — первый шаг в правильном направлении. Если вы некоторое время работали с Xcode, возможно, вы оказались в ситуации, когда вам хотелось бы, чтобы определенная задача могла быть автоматизирована в Xcode. Расширения редактора исходного кода позволяют сторонним приложениям изменять исходный файл, и это именно то, что вам нужно для ускорения вашего рабочего процесса.

На данный момент расширения могут взаимодействовать только с исходным кодом. Это означает, что не каждый плагин, доступный через Alcatraz, может быть заменен расширением редактора исходного кода. Но кто знает, что принесет будущее.

Важно понимать, что каждое расширение должно содержаться в приложении macOS. Например, вы можете добавить настройки и пояснения о том, что расширение делает в приложении macOS, и распространять его через Mac App Store. Также обратите внимание, что каждое расширение работает в отдельном процессе . Если расширение дает сбой, это не приведет к сбою Xcode. Вместо этого будет отображаться сообщение о том, что расширение не смогло завершить свою работу.

Кроме того, расширения не имеют пользовательского интерфейса, и они могут изменять код только тогда, когда пользователь вызывает вашу команду. Они не могут, например, работать в фоновом режиме.

Я рекомендую посмотреть сеанс WWDC 2016 о расширениях редактора исходного кода . Он не только объясняет, как начать разработку расширений редактора исходного кода Xcode, но также показывает советы и ярлыки для ускорения вашей разработки.

В этом уроке мы собираемся разработать расширение, которое очищает синтаксис замыкания в Swift. XCode автоматически завершает синтаксис закрытия с круглыми скобками, но они могут быть опущены для краткости. Это задача, которую можно легко автоматизировать, обернув ее в расширение редактора исходного кода.

Более короткое объяснение того, что мы собираемся разработать, — это расширение, которое превращает любое замыкание в более простой и понятный синтаксис. Посмотрите на пример ниже.

1
2
3
4
5
6
7
8
9
// Before
session.dataTask(with: url) { (data, response, error) in
     
}
 
// After
session.dataTask(with: url) { data, response, error in
     
}

Само собой разумеется, что это руководство требует Xcode 8. Вы можете скачать его с сайта разработчика Apple . Он работает как на OS X 10.11, так и на macOS 10.12.

Создайте новый проект OS X типа Приложения Какао и дайте ему имя CleanClosureSyntax . Убедитесь, что вы установили язык проекта на Swift . Мы будем использовать новый синтаксис Swift 3 в этом уроке.

Создайте новый проект приложения какао в Xcode 8

Мы пока оставим приложение macOS пустым, и мы можем сосредоточиться на создании расширения редактора исходного кода Xcode. В меню File выберите New> Target …. На левой боковой панели выберите OS X и выберите Xcode Source Editor Extension из списка.

Добавить новую цель типа Xcode Source Editor Extension

Нажмите « Далее» и установите « Имя продукта» на « Очиститель» . Новая цель будет создана для вас. Нажмите « Активировать», если Xcode спросит вас, следует ли активировать вновь созданную схему .

Давайте сначала проанализируем то, что Xcode только что создал для нас. Разверните папку Cleaner, чтобы увидеть ее содержимое.

Xcode Project Содержание и макет

Мы не будем изменять SourceEditorExtension.swift в этом руководстве, но его можно использовать для дальнейшей настройки вашего расширения. Метод extensionDidFinishLaunching() вызывается сразу после запуска расширения, чтобы при необходимости можно было выполнить любую инициализацию. Метод commandDefinitions можно использовать, если вы хотите динамически отображать или скрывать определенные команды.

SourceEditorCommand.swift — это расширение. В этом файле вы будете реализовывать логику для расширения. Метод perform(with:completionHandler:) вызывается, когда пользователь запускает ваше расширение. Объект XCSourceEditorCommandInvocation содержит свойство buffer , которое используется для доступа к исходному коду в текущем выбранном файле. Обработчик завершения должен вызываться со значением nil если все прошло хорошо, в противном случае передайте ему экземпляр NSError .

Теперь, когда проект содержит все необходимые цели, мы готовы начать писать расширение. Напомним, что мы хотим удалить скобки из любого замыкания в файле Swift. Это можно сделать в три этапа:

  • найти строки, содержащие замыкание
  • удалите две скобки из этой конкретной строки
  • заменить обратно измененную строку

Давайте начнем.

Мы можем использовать регулярное выражение (regex), чтобы проанализировать каждую строку кода и посмотреть, содержит ли оно замыкание. Вы можете обратиться к руководству Акиеля о Swift и регулярных выражениях, если хотите узнать больше о регулярных выражениях. Вы можете использовать RegExr для проверки ваших регулярных выражений. Посмотрите на следующий снимок экрана, чтобы увидеть, как я проверял свое регулярное выражение.

Regex для анализа синтаксиса закрытия

Откройте SourceEditorCommand.swift и измените метод execute perform(with:completionHandler:) completeHandler perform(with:completionHandler:) чтобы он выглядел следующим образом:

01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: (NSError?) -> Void ) -> Void {
    var updatedLineIndexes = [Int]()
         
    // 1. Find lines that contain a closure syntax
    for lineIndex in 0 ..< invocation.buffer.lines.count {
        let line = invocation.buffer.lines[lineIndex] as!
        do {
            let regex = try RegularExpression(pattern: «\\{.*\\(.+\\).+in», options: .caseInsensitive)
            let range = NSRange(0 ..< line.length)
            let results = regex.matches(in: line as String, options: .reportProgress, range: range)
            // 2. When a closure is found, clean up its syntax
            _ = results.map { result in
                let cleanLine = line.remove(characters: [«(«, «)»], in: result.range)
                updatedLineIndexes.append(lineIndex)
                invocation.buffer.lines[lineIndex] = cleanLine
            }
        } catch {
            completionHandler(error as NSError)
        }
    }
         
    // 3. If at least a line was changed, create an array of changes and pass it to the buffer selections
    if !updatedLineIndexes.isEmpty {
        let updatedSelections: [XCSourceTextRange] = updatedLineIndexes.map { lineIndex in
            let lineSelection = XCSourceTextRange()
            lineSelection.start = XCSourceTextPosition(line: lineIndex, column: 0)
            lineSelection.end = XCSourceTextPosition(line: lineIndex, column: 0)
            return lineSelection
        }
        invocation.buffer.selections.setArray(updatedSelections)
    }
    completionHandler(nil)
}

Сначала мы создаем и массив значений Int которые будут содержать индексы строк измененных строк. Это потому, что мы не хотим заменять все строки. Мы хотим заменить только те строки, которые мы модифицируем.

Мы перечисляем все строки объекта invocation.buffer и пытаемся найти соответствие для объекта RegularExpression . Если я удаляю экранирующие символы из регулярного выражения, это выглядит следующим образом:

1
{.*(.+).+in

Это регулярное выражение соответствует, когда строка имеет следующие характеристики:

  • У него есть фигурная открытая скобка ( { ), за которой следует 0 или более символов, кроме символа новой строки ( \n ).
  • Снова должна быть найдена открытая скобка ( ( ), за которой следуют 0 или более символов. Эта часть должна содержать параметры замыкания.
  • Затем нам нужно найти закрывающую скобку ( ) ), за которой следуют 0 или более символов, которые являются необязательными типами возврата.
  • Наконец, ключевое слово in должно быть найдено.

Если объект RegularExpression не может найти соответствие (например, если регулярное выражение недопустимо), мы вызываем completionHandler с ошибкой в ​​качестве параметра. Если строка, соответствующая всем этим условиям, найдена в строке, мы правильно локализовали замыкание.

Когда совпадение найдено, мы вызываем служебный метод на NSString который удаляет скобки. Нам также нужно передать диапазон соответствия, чтобы избежать удаления некоторых других скобок за пределами замыкания.

Последняя часть кода проверяет, что хотя бы строка была изменена. Если это так, мы вызываем setArray() чтобы заменить новые строки и правильные индексы. Обработчик завершения вызывается со значением nil чтобы Xcode знал, что все прошло хорошо.

Нам все еще нужно реализовать метод remove(characters:range:) в NSString . Давайте добавим это расширение вверху файла.

01
02
03
04
05
06
07
08
09
10
extension NSString {
    // Remove the given characters in the range
    func remove(characters: [Character], in range: NSRange) -> NSString {
        var cleanString = self
        for char in characters {
            cleanString = cleanString.replacingOccurrences(of: String(char), with: «», options: .caseInsensitiveSearch, range: range)
        }
        return cleanString
    }
}

Этот метод вызывает replacingOccurrences(of:with:range:) для NSString для каждого символа, который мы хотим удалить.

Xcode 8 поставляется с отличным решением для тестирования расширений. Прежде всего, если вы используете OS X 10.11 El Capitan , откройте терминал , выполните следующую команду и перезагрузите ваш Mac.

1
sudo /usr/libexec/xpccachectl

После этого соберите и запустите ваше расширение, выбрав соответствующую схему. Когда он спросит, какое приложение запустить, найдите XCode и убедитесь, что выбрана бета- версия XCode 8. Новая версия XCode будет запущена с серым значком приложения, чтобы вы могли распознать, в каком экземпляре XCode вы тестируете расширение.

В новом экземпляре Xcode создайте новый проект или откройте существующий и перейдите в « Редактор»> «Очистить закрытие»> «Команда редактора исходного кода» . Убедитесь, что в текущем файле Swift есть хотя бы одно замыкание, чтобы увидеть результат. Как вы можете видеть в следующей анимации, наше расширение работает.

Окончательный результат

Source Editor Command — это имя по умолчанию для команды. Вы можете изменить его в файле Info.plist расширения. Откройте его и измените строку на Чистый синтаксис .

Мы также можем назначить ярлык для автоматического вызова команды « Чистый синтаксис» . Откройте Настройки XCode и выберите вкладку « Привязки клавиш ». Ищите чистый синтаксис, и команда появится. Нажмите справа от него и нажмите ярлык, который вы хотите использовать, например, Command-Alt-Shift- + . Теперь вы можете вернуться к исходному файлу и нажать этот ярлык, чтобы вызвать его напрямую.

Настройки XCode для установки привязки клавиш для команды расширения

На момент написания статьи расширения Xcode 8 и редактора исходного кода все еще находятся в стадии бета-тестирования. Следующие советы помогут вам решить некоторые проблемы, с которыми вы можете столкнуться.

Если ваше расширение недоступно для выбора в экземпляре тестирования Xcode, убейте процесс com.apple.dt.Xcode.AttachToXPCService и снова запустите расширение.

В Xcode есть процесс, который можно убить, если расширение не видно

Замените только те строки, которые вы изменили в буфере. Это заставляет расширение работать быстрее, и у него будет меньше шансов быть убитым Xcode. Последнее может произойти, если Xcode считает, что команда вашего расширения занимает слишком много времени.

Если вы хотите показать несколько команд, присвойте каждой команде свой идентификатор и используйте свойство XCSourceEditorCommandInvocation объекта XCSourceEditorCommandInvocation чтобы определить, какая из них сработала пользователем.

Создать расширение редактора исходного кода Xcode очень просто. Если вы можете улучшить рабочий процесс и ускорить разработку, создав расширение редактора исходного кода, тогда сделайте это. Apple представила новый способ для разработчиков делиться подписанными плагинами через Mac App Store, чтобы вы могли выпустить свою работу и увидеть, как другие разработчики получают от нее выгоду.

Вы можете найти исходный код примера этого руководства на GitHub .