Любой разработчик, создавший собственный проблемно-ориентированный язык (DSL), корпоративный шаблон конфигураций или кодогенератор, мечтает о богатом пользовательском опыте: чтобы в любимом редакторе код подсвечивался синтаксисом, при наведении курсора всплывала документация, а ошибки подсвечивались красной волнистой линией прямо во время ввода. Однако попытка реализовать сервер по протоколу Language Server Protocol (LSP 3.17) с нуля часто разбивается о стену технической рутины: парсинг заголовков Content-Length в JSON-RPC 2.0, диспетчеризация методов, управление состоянием открытых буферов и согласование возможностей (capabilities negotiation).
Библиотека owenrumney/go-lsp на языке Go берет на себя всю низкоуровневую черную работу, предоставляя разработчику элегантную компонентную архитектуру на основе стандартных интерфейсов.
Интерфейсная модель и автоматическое согласование
Вместо монолитных диспетчеров библиотека предлагает собирать сервер как конструктор. Единственное жесткое требование — реализовать интерфейс LifecycleHandler, отвечающий за методы инициализации (Initialize) и завершения работы (Shutdown). Все остальные функции протокола подключаются реализацией опциональных интерфейсов: HoverHandler, CompletionHandler, DefinitionHandler или FormattingHandler.
Во время фазы рукопожатия библиотека сама инспектирует методы переданной структуры и автоматически формирует блок ServerCapabilities для клиента (VS Code, Neovim, Helix). Разработчик освобожден от риска рассинхронизации: если метод реализован в коде, редактор немедленно узнает о поддержке функции.
Продемонстрируем минимальный сервер, возвращающий документацию при наведении курсора:
package main
import (
"context"
"github.com/owenrumney/go-lsp/lsp"
)
type MyDSLHandler struct{}
// Обязательный метод жизненного цикла
func (h *MyDSLHandler) Initialize(ctx context.Context, params *lsp.InitializeParams) (*lsp.InitializeResult, error) {
return &lsp.InitializeResult{}, nil
}
func (h *MyDSLHandler) Shutdown(ctx context.Context) error {
return nil
}
// Реализация HoverHandler для всплывающих подсказок
func (h *MyDSLHandler) Hover(ctx context.Context, params *lsp.HoverParams) (*lsp.Hover, error) {
return &lsp.Hover{
Contents: lsp.MarkupContent{
Kind: lsp.MarkupKindMarkdown,
Value: "**MyDSL Keyword**: нативная директива оркестрации пайплайна",
},
}, nil
}
Запуск сервера и тестирование без редактора
Инициализация и запуск готового процесса сводятся к передаче потоков ввода-вывода в конструктор сервера:
package main
import (
"os"
"github.com/owenrumney/go-lsp/server"
)
func main() {
handler := &MyDSLHandler{}
srv := server.NewServer(handler)
// Запуск на стандартных потоках stdin/stdout
if err := srv.Run(os.Stdin, os.Stdout); err != nil {
os.Exit(1)
}
}
Огромным преимуществом библиотеки является встроенный тестовый пакет servertest. С его помощью можно проверять логику подсказок и валидации в стандартных unit-тестах Go через каналы в памяти (net.Pipe()), не запуская реальный процесс редактора.
Автор библиотеки Оуэн Рамни успешно применяет ее в боевых проектах make-ls (сервер для Makefiles) и goreleaser-ls. Библиотека позволяет собрать надежный специализированный языковой сервер за один вечер, опираясь на строгую типизацию Go.
