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 состоит из трёх запросов, совместно позволяющих клиентам отображать иерархический вид отношения подтипов для именованных типов.
textDocument/prepareTypeHierarchyвозвращает элемент, описывающий именованный тип в текущей позиции;typeHierarchyItem/subtypesвозвращает множество подтипов выбранного (интерфейсного) типа; иtypeHierarchy/supertypesвозвращает множество супертипов (интерфейсных типов) выбранного типа.
Вызовите команду, выделив имя типа.
Как и при запросе 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.