Gopls: Возможности навигации

Эта страница описывает возможности gopls для навигации по исходному коду.

Определение

Запрос LSP textDocument/definition возвращает местоположение объявления символа под курсором. Большинство редакторов предоставляют команду для прямого перехода к этому месту.

Запрос определения также работает в неожиданных местах:

  • На пути импорта, он возвращает список местоположений каждого объявления пакета в файлах импортируемого пакета.
  • На объявлении пакета, он возвращает местоположение объявления пакета, которое предоставляет документацию для этого пакета.
  • На символе в директиве go:linkname, он возвращает местоположение объявления этого символа.
  • На ссылке в документации, он возвращает (как hover) местоположение связанного символа.
  • На имени файла в директиве go:embed, он возвращает местоположение встраиваемого файла.
  • На объявлении функции, не написанной на Go (функции func без тела), он возвращает местоположение реализации на ассемблере, если таковая имеется,
  • На инструкции return, он возвращает местоположение переменных результата функции.
  • На инструкции goto, break или continue, он возвращает соответственно местоположение метки, закрывающую фигурную скобку соответствующего блока или начало соответствующего цикла.

Поддержка клиентами:

  • VS Code: Используйте Go to Definition (F12 или -клик). Если курсор уже находится на объявлении, запрос интерпретируется как «Go to References».
  • Emacs + eglot: используйте M-x xref-find-definitions.
  • Vim + coc.nvim: ??
  • CLI: gopls definition file.go:#offset

Ссылки

Запрос textDocument/references в LSP возвращает расположение всех идентификаторов, ссылающихся на символ под курсором.

Алгоритм поиска ссылок обрабатывает различные части синтаксиса следующим образом:

  • Ссылки на символ отмечают все использования этого символа. В случае экспортированных символов это может включать расположения в других пакетах.
  • Ссылки на объявление пакета включают все прямые импорты пакета, а также все другие объявления пакетов в том же пакете.
  • Запрос ссылок на встроенный символ, такой как int или append, является ошибкой, поскольку предполагается, что их слишком много, чтобы они были интересны.
  • Ссылки на метод интерфейса включают ссылки на конкретные типы, реализующие интерфейс. Аналогично, ссылки на метод конкретного типа включают ссылки на соответствующие методы интерфейса.
  • Встроенное поле T в структуре, такой как struct{T}, уникально в Go, поскольку оно является одновременно ссылкой (на тип) и определением (поля). Операция references сообщает только о ссылках на него как на поле. Чтобы найти ссылки на тип, сначала перейдите к его объявлению.

Имейте в виду, что результат запроса ссылок содержит информацию только о конфигурации сборки, использованной для анализа выбранного файла, поэтому если вы запросите ссылки на символ, определённый в foo_windows.go, результат никогда не будет включать файл bar_linux.go, даже если этот файл ссылается на символ с тем же именем; см. https://go.dev/issue/65755.

Клиенты могут запрашивать включать объявление среди ссылок; большинство делают это.

Поддержка клиентами:

  • VS Code: Используйте Go to References для быстрого «предварительного просмотра» ссылок, или Find all References для открытия панели ссылок.
  • Emacs + eglot: Через пакет xref: используйте M-x xref-find-references.
  • Vim + coc.nvim: ??
  • CLI: gopls references file.go:#offset

Реализация

Запрос textDocument/implementation в LSP запрашивает связь между абстрактными и конкретными типами и их методами.

Интерфейсы и конкретные типы сопоставляются с использованием наборов методов:

  • При вызове на ссылке на тип интерфейса возвращаются расположения объявлений всех типов, реализующих интерфейс.
  • При вызове на конкретном типе возвращаются расположения соответствующих типов интерфейсов.
  • При вызове на методе интерфейса возвращаются соответствующие методы типов, удовлетворяющих интерфейсу.
  • При вызове на конкретном методе возвращаются расположения соответствующих методов интерфейсов.

Например:

  • implementation(io.Reader) включает подинтерфейсы, такие как io.ReadCloser, и конкретные реализации, такие как *os.File. Также включает другие объявления, эквивалентные io.Reader.
  • implementation(os.File) включает только интерфейсы, такие как io.Reader и io.ReadCloser.

Функция Implementation в LSP имеет встроенную склонность к подтипам, возможно, потому что в языках, таких как Java и C++, связь между типом и его супертипами явно выражена в синтаксисе, поэтому соответствующая операция «Перейти к интерфейсам» может быть выполнена как последовательность из двух или более шагов «Перейти к определению»: первый — для посещения объявления типа, а остальные — для последовательного посещения предков. (См. https://github.com/microsoft/language-server-protocol/issues/2037.)

В Go, где между двумя типами нет синтаксической связи, при навигации в любом направлении между подтипами и супертипами требуется поиск. Приведённая выше эвристика хорошо работает во многих случаях, но невозможно запросить суперинтерфейсы io.ReadCloser. Для более явной навигации между подтипами и супертипами используйте функцию [Type Hierarchy](#Type Hierarchy).

Рассматриваются только нетривиальные интерфейсы; для типа any реализации не отображаются.

Внутри одного пакета перечисляются все соответствующие типы/методы. Однако при переходе между пакетами отображаются только экспортируемые типы уровня пакета и их методы, поэтому локальные типы (как интерфейсы, так и структуры с методами, связанными через встраивание) могут отсутствовать в результатах.

Функции, func типы и динамические вызовы функций сопоставляются по сигнатурам:

  • При вызове на токене func в определении функции возвращаются расположения соответствующих типов сигнатур и динамических выражений вызова.
  • При вызове на токене func в типе сигнатуры возвращаются расположения соответствующих определений конкретных функций.
  • При вызове на токене ( в динамическом вызове функции возвращаются расположения соответствующих определений конкретных функций.

Если целевой тип или кандидатный тип являются дженериками, то результаты будут включать кандидатный тип, если существует какая-либо инстанциация двух типов, которая позволила бы одному из них реализовать другой. (Примечание: совпадение не реализует полную унификацию, поэтому параметры типа рассматриваются как подстановочные знаки, которые могут соответствовать произвольным типам, без учёта согласованности подстановок в наборе методов или даже внутри одного метода. Это может привести к появлению случайных совпадений.)

Поскольку тип может быть одновременно функциональным типом и именованным типом с методами (например, http.HandlerFunc), он может участвовать в обоих видах запросов реализации (по наборам методов и по сигнатурам функций). Запросы, использующие наборы методов, должны вызываться на типе или имени метода, а запросы, использующие сигнатуры, должны вызываться на токене func или (.

Поддержка клиентов:

  • VS Code: Используйте Go to Implementations (⌘F12).
  • Emacs + eglot: Используйте M-x eglot-find-implementation.
  • Vim + coc.nvim: ??
  • CLI: gopls implementation file.go:#offset

Определение типа

Запрос LSP textDocument/typeDefinition возвращает местоположение типа выбранного символа.

Например, если выбор — это имя buf локальной переменной типа *bytes.Buffer, то запрос typeDefinition вернёт местоположение типа bytes.Buffer. Клиенты обычно переходят к этому местоположению.

Конструкторы типов, такие как указатель, массив, срез, канал и карта, удаляются из выбранного типа при поиске именованного типа. Например, если x имеет тип chan []*T, то указанное определение типа будет для T. Аналогично, если тип символа — функция с одним «интересным» (именованным, не-error) типом результата, используется тип результата функции.

В настоящее время gopls требует, чтобы запрос typeDefinition применялся к символу, а не к произвольному выражению; см. https://go.dev/issue/67890 для возможных расширений этой функциональности.

Поддержка клиентов:

  • VS Code: Используйте Go to Type Definition.
  • Emacs + eglot: Используйте M-x eglot-find-typeDefinition.
  • Vim + coc.nvim: ??
  • CLI: не поддерживается

Символ документа

Запрос textDocument/documentSymbol протокола LSP сообщает список объявлений верхнего уровня в этом файле. Клиенты могут использовать эту информацию для отображения обзора файла и индекса для более быстрой навигации.

Gopls отвечает типом DocumentSymbol, если клиент указывает hierarchicalDocumentSymbolSupport; в противном случае он возвращает SymbolInformation.

Поддержка клиентами:

  • VS Code: Используйте панель «Обзор» для навигации.
  • Emacs + eglot: Используйте M-x imenu для перехода к символу.
  • Vim + coc.nvim: ??
  • CLI: gopls links file.go

Символ

Запрос workspace/symbol протокола LSP осуществляет поиск по индексу всех символов в рабочей области.

Алгоритм сопоставления символов по умолчанию (fastFuzzy), вдохновлённый популярным нечётким сопоставителем FZF, пытается выполнить различные нечёткие соответствия, чтобы исправить опечатки или аббревиатуры в запросе. Например, он считает DocSym соответствующим DocumentSymbol.

Настройки:

  • Настройка symbolMatcher управляет алгоритмом, используемым для сопоставления символов.
  • Настройка symbolStyle управляет способом квалификации символов в ответах.
  • Настройка symbolScope определяет область запроса.
  • Настройка directoryFilters указывает директории, которые должны быть исключены из поиска.

Поддержка клиентов:

  • VS Code: Используйте ⌘T, чтобы открыть Go to Symbol с областью видимости рабочей области. (Альтернативно, используйте Ctrl-Shift-O, и добавьте префикс @ для поиска внутри файла или префикс # для поиска по всей рабочей области.)
  • Emacs + eglot: Используйте M-x xref-find-apropos для отображения символов, соответствующих поисковому запросу.
  • Vim + coc.nvim: ??
  • CLI: gopls links file.go

Диапазон выделения

Запрос textDocument/selectionRange LSP возвращает информацию о лексическом охвате каждого элемента синтаксиса, окружающего текущее выделение. Клиенты могут использовать его для предоставления операции расширения выделения до всё более крупных выражений.

Поддержка клиентов:

  • VSCode: Используйте ⌘⇧^→ для расширения выделения или ⌘⇧^← для его сокращения; посмотрите это видео.
  • Emacs + eglot: Не стандартно. Используйте M-x eglot-expand-selection, определённый в этом фрагменте конфигурации.
  • Vim + coc.nvim: ??
  • CLI: не поддерживается

Иерархия вызовов

Механизм LSP CallHierarchy состоит из трёх запросов, которые вместе позволяют клиентам отображать иерархический вид части статической вызовной графики:

  • textDocument/prepareCallHierarchy возвращает список элементов для заданной позиции, каждый из которых представляет именованную функцию или метод, окружающий позицию;
  • callHierarchyItem/incomingCalls возвращает множество точек вызова, вызывающих выбранный элемент; и
  • callHierarchy/outgoingCalls возвращает множество функций, вызываемых выбранным элементом.

Вызовите команду, выделив имя в объявлении функции.

Динамические вызовы не включены, поскольку их анализирование технически невозможно. Поэтому следует учитывать, что результаты могут быть неполными, и при необходимости выполните запрос References.

Иерархия не считает вложенную функцию отличной от своей содержащей именованной функции. (Без возможности обнаружения динамических вызовов, это имело бы мало смысла.)

Скриншот ниже показывает дерево исходящих вызовов, корнем которого является f. Дерево было развернуто, чтобы показать путь от f к методу String интерфейса fmt.Stringer через внутренности fmt.Sprint:

Поддержка клиентов:

  • VS Code: элемент меню Show Call Hierarchy (⌥⇧H) открывает представление иерархии вызовов (заметьте: документация ссылается на C++, но идея та же и для Go).
  • Emacs + eglot: не является стандартным; установите с помощью (package-vc-install "https://github.com/dolmens/eglot-hierarchy"). Используйте M-x eglot-hierarchy-call-hierarchy для отображения прямых входящих вызовов выбранной функции; используйте префиксный аргумент (C-u) для отображения прямых исходящих вызовов. Расширить дерево невозможно.
  • CLI: gopls call_hierarchy file.go:#offset показывает исходящие и входящие вызовы.

Type Hierarchy

Механизм TypeHierarchy в LSP состоит из трёх запросов, совместно позволяющих клиентам отображать иерархический вид отношения подтипов для именованных типов.

Вызовите команду, выделив имя типа.

Как и при запросе Implementation, запрос иерархии типов отображает только локальные в функции типы внутри того же пакета, что и запрашиваемый тип. Также результат не включает псевдонимы типов, только определённые типы.

Оговорки:

  • Иерархия типов поддерживает только именованные типы и их отношение присваиваемости. В отличие от этого, запрос Implementations также сообщает о соотношении между анонимными типами func и объявлениями функций, литералами функций, а также динамическими вызовами значений этих типов.

Поддержка клиентов:

  • VS Code: элемент меню Show Type Hierarchy открывает представление иерархии типов (заметьте: документация ссылается на Java, но идея та же самая и для Go).
  • Emacs + eglot: поддержка добавлена в марте 2025 года. Используйте M-x eglot-show-call-hierarchy.
  • CLI: пока не поддерживается.

Исходные файлы этой документации можно найти в golang.org/x/tools/gopls/doc.

GoRu.dev Golang на русском

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