Sublime Text plugin
Sublime Text plugin

Завершаем серию статей о плагинах для explain.tensor.ru -  сервиса визуализации PostgreSQL-планов. На этот раз речь пойдет о плагине для Sublime Text.


Установка

К сожалению в зависимостях нет возможности установить связанные плагины:

Dependencies are not a way to install multiple related packages. Currently no such functionality exists.

Поэтому сначала устанавливаем плагин SQLTools , а затем выполняем установку нашего плагина, используя Package Control (Ctrl+Shift+P и набираем команду Install Package):

установка плагина
установка плагина

Плагин также можно установить вручную - скачиваем с сайта explain.tensor.ru в разделе Download -> Plugins , открываем каталог в Sublime -> Preferences -> Browse Packages и распаковываем в нем архив, плагин сразу готов к использованию.

Форматирование запроса

В меню Edit или в контекстном меню редактора SQL выбираем Format SQL :

форматирование запроса
форматирование запроса

Анализ запроса

В меню Edit или в контекстном меню редактора SQL выбираем Explain Analyze ,
при этом запрос выполнится на сервере PG (настроенном в SQLTools) и полученный план отправится на анализ в сервис explain.tensor.ru через публичное API , чтобы открыть результат кликнем по ссылке в popup-окне:

анализ запроса
анализ запроса

Настройка

В меню Preferences -> Package Settings -> Explain PostgreSQL -> Settings можно поменять URL сайта, опции анализа и способ получения результата - по умолчанию показывает ссылку в popup-окне.

настройки
настройки

Разработка

Создаем новый плагин Tools -> Developer -> New Plugin... в нем регистрируется текстовая команда "example" , при вызове которой в текст документа будет вставлено "Hello, World":

import sublime
import sublime_plugin

class ExampleCommand(sublime_plugin.TextCommand):
	def run(self, edit):
		self.view.insert(edit, 0, "Hello, World!")

Сохраняем плагин в Packages/User/test.py при этом Sublime автоматически его подгрузит, проверяем - создаем новое окно Ctrl+N , открываем консоль python Ctrl+` и набираем команду, которая производит вызов метода view.insert :

view.run_command("example")

При построении имени команды Sublime берет имя класса, убирает ключевое слово Command и разделяет слова символами подчеркивания, таким образом для команды format_sql нужно название класса FormatSqlCommand .

При создании класса команды можно наследоваться от базовых классов:

sublime_plugin.TextCommand - такие команды доступны только при открытой панели, соответственно в атрибуте self.view получают панель в которой они созданы, а в методе run получают объект edit.

sublime_plugin.WindowCommand - команды доступны всегда и имеют доступ ко всему окну через self.window

Чтобы не перегружать меню лишними командами, в классе команды рекомендуется определить метод is_visible , в котором сделать зависимость от синтаксиса текущего окна, в нашем случае - SQL файлы:

def check_is_visible(view):
	syntax = view.settings().get("syntax")
	if (syntax.endswith("SQL.sublime-syntax")):
		return True
	else:
		return False

В процессе публикации были получены рекомендации заменить проверку синтаксиса на метод view.match_selector , так как имя файла синтаксиса может поменяться

def check_is_visible(view):
  return view.match_selector(0, 'source.sql')

В команде форматирования запроса используем метод view.sel для получения выделенного текста, а в случае отсутствия берем весь текст в окне. Затем, используя API explain.tensor.ru, получаем преобразованный текст и заменяем его методом view.replace :

class EpFormatSqlCommand(sublime_plugin.TextCommand):
	def is_visible(self):
		return check_is_visible(self.view)

	def run(self, edit):
		v = self.view
		selection = v.sel()[0]
		if selection.empty():
			selection = sublime.Region(0, v.size())
			text = v.substr(selection)
		else:
			text = v.substr(selection)
		data = {"query_src": text}
		url = get_plugin_settings("api_url") + "/beautifier-api"
		res = send_post_request(url, data, True)
        v.replace(edit, selection, res['btf_query_text'])
        sublime.status_message("Text formatted")

В команде анализа запроса импортируем методы плагина SQLTools , получаем текст запроса из панели, при отсутствии подключения к PostgreSQL выполняем метод SQLTools.selectConnectionQuickPanel для установки коннекта, затем методом get_explain_cmd получаем префикс запроса (EXPLAIN с опциями из настроек) и выполняем запрос методом SQLTools.conn.Command.createAndRun .

В результате получаем план запроса, который отправляем в API explain.tensor.ru и в ответ получаем URL со страницей анализа, который открываем сразу в браузере или показываем в popup-окне в зависимости от настроек:

import SQLTools.SQLTools

class EpExplainAnalyzeCommand(sublime_plugin.TextCommand):
	def is_visible(self):
		return check_is_visible(self.view)

	def run(self, edit):
		if (check_deps() == False): return
		ST = SQLTools.SQLTools.ST
		v = self.view
		selection = v.sel()[0]
		if selection.empty():
			selection = sublime.Region(0, v.size())
			text = v.substr(selection)
		else:
			text = v.substr(selection)

		def cb(result):
			data = {"query": text, "plan": result}
			api_url = get_plugin_settings("api_url")
			explain_api_url = api_url + "/explain"
			url = send_post_request(explain_api_url, data)
			plan_url = api_url + url
			show_link = get_plugin_settings("show_link")
			if (show_link == 'browser'):
				webbrowser.open_new_tab(plan_url)
			elif (show_link == 'popup_text'):
				show_popup(self.view, get_text_html(plan_url))

		if not ST.conn:
			ST.selectConnectionQuickPanel(callback=lambda: sublime.active_window().run_command('ep_explain_analyze'))
			return

		args=ST.conn.buildArgs()
		args.append("-At")
		env=ST.conn.buildEnv()
		text = get_explain_cmd() + text
		v.window().status_message("Getting plan...")
		ST.conn.Command.createAndRun(args=args,	env=env, callback=cb, query=text, timeout=60, silenceErrors=True, stream=False)

Настройки плагина хранятся в файле "Packages/Explain PostgreSQL/Explain PostgreSQL.sublime-settings", при этом персональные настройки хранятся в каталоге Packages/User в одноименном файле.

Для загрузки настроек плагина в словарь store используем метод sublime.load_settings :

store = None

def load_plugin_settings():
	global store
	store = sublime.load_settings("Explain PostgreSQL.sublime-settings")

Так как наш плагин зависит от наличия установленного SQLTools , добавим в функцию plugin_loaded проверку его наличия и в случае отсутствия покажем сообщение об ошибке и предложим установить:

if ('Packages/SQLTools/package-metadata.json' not in sublime.find_resources('package-metadata.json')):
    sublime.error_message('"Explain PostgreSQL" plugin requires "SQLTools" plugin installed.')
    sublime.run_command('install_package')

Для добавления пунктов в главное меню Edit создаем файл Main.sublime-menu :

[  
    {  
        "id": "edit",
        "children":
            [
                {
                    "caption": "Format SQL",
                    "command": "ep_format_sql",
                },
                {
                    "caption": "Explain Analyze",
                    "command": "ep_explain_analyze",
                }
            ]
    }
]

Контекстное меню в файле Context.sublime-menu :

[  
    {
	"caption": "Format SQL",
	"command": "ep_format_sql"
    },
    {
	"caption": "Explain Analyze",
	"command": "ep_explain_analyze"
    }
]

Для добавления команд в Command Palette (Ctrl+Shift+P) создаем Default.sublime-commandsс перечнем команд:

[
    {
    	"caption": "Explain PostgreSQL: Format SQL",
    	"command": "ep_format_sql"
    },
    {
    	"caption": "Explain PostgreSQL: Explain Analyze",
    	"command": "ep_explain_analyze"
    }
]

Комбинации клавиш для вызова команд хранятся в файле Default.sublime-keymap если они одинаковые для всех платформ, иначе - в отдельном файле для каждой платформы: Default (PLATFORM).sublime-keymap , где PLATFORM - это Window, Linux или OSX. Например для Linux и Windows (для MacOS ctrl заменен на command):

[
    {
        "keys": [ "ctrl+shift+g" ],
        "command": "ep_format_sql",
        "context": [
            { "key": "selector", "operand": "source.sql"}
        ]
    },
    {
        "keys": [ "ctrl+shift+e" ],
        "command": "ep_explain_analyze",
        "context": [
            { "key": "selector", "operand": "source.sql"}
        ]
    }
]

Здесь добавили условие контекста - в панели должен быть открыт файл SQL.

Публикация плагина

Менеджером пакетов для Sublime является Package Control .

Для публикации плагина форкаем Package Control Channel , в его каталоге repository в файле e.json добавляем наш плагин:

{
    "name": "Explain PostgreSQL",
    "details": "https://github.com/MGorkov/explain-postgresql-sublime",
    "labels": ["sql", "postgresql", "formatting", "formatter", "explain", "tools"],
    "releases": [
        {
            "sublime_text": ">=3000",
            "tags": true
        }
    ]
}

Запускаем тесты:

тесты
тесты

И делаем Pull Request для добавления изменений с нашим плагином в оригинальный проект.

По умолчанию запуск бота для выполнения code review происходит вручную, вот пояснения:

Due to people using Github's Action runners to mine cryptocurrencies and do other ignoble stuff, they must now be approved for each new contributor once before they are run. I've done that for your PR.

Дожидаемся запуска review , исправляем замечания и после завершения PR появляется возможность установки плагина через Package Control.

Код плагина опубликован под лицензией MIT.

Комментарии (2)


  1. dx-77
    25.03.2024 07:06

    def check_is_visible(view):

    syntax = view.settings().get("syntax")

    return syntax.endswith("SQL.sublime-syntax")


    1. MGorkov Автор
      25.03.2024 07:06

      да, в итоге все равно поменяли на match_selector