- PVSM.RU - https://www.pvsm.ru -

Для написания статей на Хабр я пользуюсь текстовым редактором Sublime Text. Почему это хороший редактор на Хабре уже много раз писали (например тут [1]). Есть, однако, при написании статьи момент, когда её нужно переносить для публикации на Хабр, ну, знаете: Хабр->Добавить пост->Название, хабы, текст (Ctrl+C/Ctrl+V), метки, предпосмотр. В этот момент оказывается, что как-то текст с картинками сверстался на Хабре некрасиво, начинаются правки. Править в браузере? Неудобно и небезопасно. Править в Sublime и постоянно копипастить? Неудобно и надоедает.
Поэтому я сделал для себя небольшой плагин к Sublime, который умеет взаимодействовать с Хромом и Хабром, перекидывая по хоткею в Sublime написанный текст в редактор на странице создания нового топика с автоматическим нажиманием кнопки «Предпросмотр». Это позволяет писать статью в Sublime и в одно нажатие видеть результат её отображения на Хабре.
Под катом мы научимся писать плагины к Sublime и разберёмся как из питоновского кода взаимодействовать с Хромом, используя его протокол удалённой отладки. Полный код плагина на GitHub [2] прилагается.
Плагин под Sublime Text 3 и Google Chrome. С минимальными правками он может быть адаптирован под Sublime Text 2 и с чуть большими — под Firefox. Просто мне это не очень интересно, а кому хочется — ну, вы в курсе, как работает кнопочка Fork [3] на GitHub.
Нет ничего проще. Запускаем Sublime Text, идём в меню — "Tools -> New Plugin…" и получаем новый текстовый файл со следующим содержанием:
import sublime, sublime_plugin
class ExampleCommand(sublime_plugin.TextCommand):
def run(self, edit):
self.view.insert(edit, 0, "Hello, World!")
Нажимаем Ctrl+S и видим, что диалог сохранения файла предлагает нам сохранить его в папку User в профиле пользователя. Не соглашаемся, переходим на один уровень вверх (в папку Packages), создаем здесь папку SublimeHabrPlugin и сохраняем наш файл в эту папку с именем SublimeHabrPlugin.py. Прелесть Sublime Text в том, что ничего не надо билдить, подключать, перезапускать. В момент сохранения файла автоматически произойдет его подгрузка в редактор. Проверить это можно открыв консоль Sublime Text (нажмите Ctrl+~), в ней должно быть написано что-то типа
reloading plugin HabrPlugin.HabrPlugin
теперь можно выполнить в этой же консоли команду
view.run_command('example')
и убедиться, что в текст текущего файла добавилась фраза «Hello, World!». Название команды «example» получилось из имени класса ExampleCommand путем отбрасывания суффикса «Command» и преобразования оставшейся части из CamelCase в underscore_case. Т.е. для добавления команды «habr» наш класс должен называться HabrCommand.
Каким же образом мы будем перекидывать наш текст из Sublime Text в Google Chrome, да и ещё не просто «куда-нибудь», а именно в поле редактора текста на странице нового топика Хабра, с последующим нажатием кнопки «Предпросмотр»? Здесь нам на помощь приходит такая штука, как Chrome Remote debugging protocol [4]. Это протокол, который позволяет подключится к Хрому, получить список открытых вкладок и полный контроль над каждой из них (модификацию контента, подписку на события, выполнениe JavaScript, установка breakpoint'ов) — т.е. фактически всё то, что позволяют делать «инструменты разработчика», которые по F12 открываются. Я открою даже более страшный секрет — сами эти инструменты именно по этому протоколу и работают, что даёт возможность подключится из одного Хрома к отладке страниц в другом Хроме, на удалённой машине или в мобильной версии.
Для разрешения внешних подключений нам нужно запускать Хром с параметром командной строки
chrome.exe --remote-debugging-port=9222
Лично я сделал себе ярлычек «Статья на Хабр» на рабочем столе, запускающий хром вот так:
«C:Program Files (x86)GoogleChromeApplicationchrome.exe» --remote-debugging-port=9222 habrahabr.ru/topic/add/ [5]
В итоге в один клик получаем открытую страницу новой статьи и Хром запущенный с возможностью подключения для удалённной отладки.
Идея плагина уже понятна. Мы будем брать весь текст текущего файла, подключаться к Хрому, получать список вкладок, коннектится к вкладке с открытой страницей Хабра по протоколу WebSockets и выполнять Javascript, который вставит текст в нужное поле и нажмет кнопку «Предпросмотр». Итак, идём по шагам.
text = self.view.substr(sublime.Region(0, self.view.size()))
Нам нужно подключиться по HTTP к локальной машине на порт 9222, получить JSON с метаданными, распарсить его и получить из него ссылку на строку подключения к вкладке с Хабром по протоколу WebSockets.
import json
import urllib
import sys
def download_url_to_string(url):
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
html = response.read()
return html
info_about_tabs = download_url_to_string('http://localhost:9222/json')
info_about_tabs = info_about_tabs.decode("utf-8")
decoded_data = json.loads(info_about_tabs)
first_tab_websocket_url = decoded_data[0]['webSocketDebuggerUrl']
print(first_tab_websocket_url)
Если этот код запустить — в консоли будет выведено что-то типа
ws://localhost:9222/devtools/page/FD3D0027-0D2D-4DD0-AD1C-156FCA561F7E
Это и есть URL для подключения к отладке вкладки.
Протокол WebSocket здесь выбран Хромом из-за его удобства в двухсторонних коммуникациях — сервер может посылать клиенту нотификации о происходящих событиях немедленно, не дожидаясь его следующего запроса. Поддержка WebSocket, в отличии от HTTP, не входит в стандартную библиотеку Python, поэтому нам придётся взять стороннюю библиотеку. Я нашел вот эту [6], она вполне справляется с поставленными задачами.
Итак, подключаемся к нужной вкладке и выполняем, для начала, в её контексте простой Javascript:
import json
import urllib
import SublimeHabrPlugin.websocket
def send_to_socket(socket, data):
socket.send(data)
def send_script_to_socket(socket, script):
send_to_socket(socket, '{"id": 1, "method": "Runtime.evaluate", "params": { "expression": "' + script + '", "returnByValue": false}}')
def on_open(ws):
print('Websocket open')
send_script_to_socket(ws, 'alert('Гав!')')
def on_message(ws, message):
decoded_message = json.loads(message)
print(decoded_message)
ws.close()
def connect_to_websocket(url):
res = SublimeHabrPlugin.websocket.WebSocketApp(url, on_message = on_message)
res.on_open = on_open
return res
first_tab_websocket = connect_to_websocket(first_tab_websocket_url)
first_tab_websocket.run_forever()
JavaScript, который вставляет текст в редактор на Хабре и нажимает кнопку «Предпросмотр»
Ну, это совсем просто:
document.getElementById('text_textarea').innerHTML = 'text';
document.getElementsByName('preview')[0].click();
Ну и теперь всё это вместе.
import sublime, sublime_plugin
import json
import urllib
import SublimeHabrPlugin.websocket
import sys
import base64
def download_url_to_string(url):
request = urllib.request.Request(url)
response = urllib.request.urlopen(request)
html = response.read()
return html
def send_to_socket(socket, data):
socket.send(data)
def send_script_to_socket(socket, script):
send_to_socket(socket, '{"id": 1, "method": "Runtime.evaluate", "params": { "expression": "' + script + '", "returnByValue": false}}')
def on_open(ws):
text_to_send = str(base64.b64encode(bytes(ws.text, "utf-8")), encoding='UTF-8')
send_script_to_socket(ws, 'document.getElementById('text_textarea').innerHTML = atob('' + text_to_send + '');document.getElementsByName('preview')[0].click();')
def on_message(ws, message):
decoded_message = json.loads(message)
ws.close()
def connect_to_websocket(url):
res = SublimeHabrPlugin.websocket.WebSocketApp(url, on_message = on_message)
res.on_open = on_open
return res
class HabrCommand(sublime_plugin.TextCommand):
def run(self, edit):
text = self.view.substr(sublime.Region(0, self.view.size()))
info_about_tabs = download_url_to_string('http://localhost:9222/json')
info_about_tabs = info_about_tabs.decode("utf-8")
decoded_data = json.loads(info_about_tabs)
first_tab_websocket_url = decoded_data[0]['webSocketDebuggerUrl']
print(first_tab_websocket_url)
first_tab_websocket = connect_to_websocket(first_tab_websocket_url)
first_tab_websocket.text = text
first_tab_websocket.run_forever()
Для того, чтобы не ломать голову в экранировании всяких кавычек, слешей и прочих спецсимволов в тексте при передаче из питона в Javascript я просто заворачиваю в питоновском коде всё в Base64 и разворачиваю обратно в Javascript (благо, поддержка Base64 входит в стандартные библиотеки обоих языков).
Открываем «Preferences -> Key Bindings — User». Дописываем туда:
{ "keys": ["alt+shift+h"], "command": "habr" }
Запускаем Хром, открываем Sublime Text, пишем «Test», нажимаем alt+shift+h:

Проект на GitHub [2]
Автор: tangro
Источник [7]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/python/57839
Ссылки в тексте:
[1] тут: http://habrahabr.ru/post/147856/
[2] код плагина на GitHub: https://github.com/ezhikus/SublimeHabrPlugin
[3] Fork: https://github.com/ezhikus/SublimeHabrPlugin/fork
[4] Chrome Remote debugging protocol: https://developers.google.com/chrome-developer-tools/docs/debugger-protocol
[5] habrahabr.ru/topic/add/: http://habrahabr.ru/topic/add/
[6] вот эту: https://pypi.python.org/pypi/websocket-client-py3/0.12.0
[7] Источник: http://habrahabr.ru/post/217121/
Нажмите здесь для печати.