reangd (reangd) wrote in ru_emacs,
reangd
reangd
ru_emacs

Emacs как IDE для Go

(оригинал: ReanGDblog)

Цель статьи дать обзор инструментов в Emacs для работы с Go кодом. Настроить горячие клавиши, возможно добавить алиасы и сделать их удобнее для повседневного использования - на вашей совести. Хочу отметить, что поддержка языка в Emacs - на высоком уровне: подсветка кода, автодополнение, сниппеты, рефакторинг, подсветка ошибок, отображение документации, тестирование, компиляция и многое другое. Сразу оговорюсь, что проверял я только под Linux, под альтернативные OS могут быть особенности, которые тут не освещены.

Общая настройка Go

Надеюсь у вас уже стоит go, правильно настроены "GOPATH" и т.п., поскольку отдельные плагины чувствительны к подобного рода вещам. Не забудьте добавить "$GOPATH/bin" в PATH, что бы утилиты, который будем ставить, запускались без указания полного пути. Так же я рассчитываю, что с основами Emacs и Go вы знакомы.

Вся настройка в одном месте

Сначала коротко о настройке. Устанавливаем набор приложений, которые необходимы плагинам:

go get -u github.com/nsf/gocode
go get -u github.com/rogpeppe/godef
go get -u github.com/jstemmer/gotags
go get -u github.com/kisielk/errcheck
go get -u golang.org/x/tools/cmd/guru
go get -u github.com/golang/lint/golint
go get -u golang.org/x/tools/cmd/gorename
go get -u golang.org/x/tools/cmd/goimports
sudo go get -u golang.org/x/tools/cmd/godoc

Обратите внимание, что "godoc" нуждается в "sudo", т.к. ставятся в системную директорию, остальные будут установлены в локальный "GOPATH". Я бы посоветовал ставить "godoc" последним, что бы он не создавал директорий с правами root.

Из плагинов Emacs понадобятся следующие: go-mode, go-eldoc, company, company-go, yasnippet, go-rename, multi-compile, flycheck, gotest, go-scratch, go-direx, go-guru.
Полная настройка специфичная для go-mode режима выглядит у меня вот так:

(require 'company)
(require 'flycheck)
(require 'yasnippet)
(require 'multi-compile)
(require 'go-eldoc)
(require 'company-go)

(add-hook 'before-save-hook 'gofmt-before-save)
(setq-default gofmt-command "goimports")
(add-hook 'go-mode-hook 'go-eldoc-setup)
(add-hook 'go-mode-hook (lambda ()
                            (set (make-local-variable 'company-backends) '(company-go))
                            (company-mode)))
(add-hook 'go-mode-hook 'yas-minor-mode)
(add-hook 'go-mode-hook 'flycheck-mode)
(setq multi-compile-alist '(
    (go-mode . (
("go-build" "go build -v"
   (locate-dominating-file buffer-file-name ".git"))
("go-build-and-run" "go build -v && echo 'build finish' && eval ./${PWD##*/}"
   (multi-compile-locate-file-dir ".git"))))
    ))

Go mode

Теперь по порядку. Go-mode - базовый пакет, вокруг которого строится остальное. Кроме подсветки, он приносит с собой поддержку команд:

  • M-x godef-jump (C-c C-j) - перейти к реализации функции под курсором (вернуться назад, можно через M-*)

  • M-x godef-jump-other-window (C-x 4 C-c C-j) - аналогично "godef-jump" только открывается в новом окне

  • M-x godoc-at-point - покажет документацию по команде под курсором

  • M-x go-goto-imports (C-c C-f i) - перейти к секции "import" текущего файла

  • M-x go-goto-function (C-c C-f f) - перейти к началу функции, внутри которой находится курсор

  • M-x go-goto-arguments (C-c C-f a) - перейти к аргументам текущей функции

  • M-x go-goto-docstring (C-c C-f d) - перейти к комментариям функции

  • M-x go-goto-return-values (C-c C-f r) - перейти к описанию возвращаемого значения для функции

  • M-x beginning-of-defun (C-M-a) - перейти к началу функции

  • M-x end-of-defun (C-M-e) - перейти к концу функции

  • Есть базовая поддержка imenu для функций и типов

Форматирование исходников я делаю через "goimports", который установили выше. Он помимо собственно форматирования, умеет добавлять необходимые импорты в текущем файле и вычищать неиспользуемые. Удобно вызывать его автоматически, при сохранении:

(add-hook 'before-save-hook 'gofmt-before-save)
(setq-default gofmt-command "goimports")

Вручную вызывается через "M-x gofmt".
Вот как это выглядит:

Go eldoc

Плагин go-eldoc - умеет показывать в строке состояния информацию о переменной или аргументе\возвращаемом значении функции находящейся под курсором. Фактически документация по сигнатурам. Скриншот я нагло украл у автора:
Для настройки добавьте вот такие строки:

(require 'go-eldoc)
(add-hook 'go-mode-hook 'go-eldoc-setup)

Автодополнение

Я предпочитаю для автодополнения company, поэтому ставим ещё company-go и добавляем в конфиг:

(require 'company)
(require 'company-go)
(add-hook 'go-mode-hook (lambda ()
                            (set (make-local-variable 'company-backends) '(company-go))
                            (company-mode)))

Это единственная специфичная настройка для Go, сам "company", кто пользуется им, настраивать умеют.


Если вам нравится auto-complete, то для go понадобиться go-autocomplete, а настройка описана вот тут.

Сниппеты

Тут стандарт - yasnippet, который поставляется с поддержкой Go, если у вас "yasnippet" не включён глобально, достаточно добавить хук для go-mode:

(require 'yasnippet)
(yas-reload-all)
(add-hook 'go-mode-hook 'yas-minor-mode)

Доступные сниппеты смотрите в репозитории. Если вас они не удовлетворяют, существуют альтернативные наборы: yasnippet-go и go-snippets, но там, на мой вкус, ничего интересного.

Переименование функций и структур

Переименование самый используемый из методов рефакторинга. Он поддерживается плагином go-rename и вызывается при помощи "M-x go-rename". Из недостатков - go-rename не работает, если в проекте есть синтаксические ошибки.

Подсветка ошибок

Сильно ускоряет разработку - подсветка ошибок до компиляции. Для этого ставим flycheck и делаем общие настройки, по необходимости. Затем переключаемся в буфер в котором включен режим go-mode и проверяем, что flycheck видит все необходимые утилиты: "M-x flycheck-verify-setup", у меня получилось так:

  go-gofmt
    - predicate:  t
    - executable: Found at /usr/bin/gofmt
  go-golint
    - predicate:  t
    - executable: Found at /home/user/go/bin/golint
  go-vet
    - predicate:  t
    - executable: Found at /usr/bin/go
  go-build
    - predicate:  t
    - executable: Found at /usr/bin/go
  go-test
    - predicate:  nil
    - executable: Found at /usr/bin/go
  go-errcheck
    - predicate:  t
    - executable: Found at /home/user/go/bin/errcheck
Flycheck Mode is enabled.

Если что-то не найдено, то вы поставили не все утилиты из списка в начале статьи или не добавили их в PATH. go-test тут не включён, т.к. он срабатывает только для файлов имя которых оканчивается на "_test.go".

Настройка заключается в том, что бы включить "flycheck-mode" для режима go:

(require 'flycheck)
(add-hook 'go-mode-hook 'flycheck-mode)

Об общих настройках flycheck, я надеюсь рассказывать не нужно.


Поклонники flymake - сделают аналогичное при помощи пакета flymake-go (инструкции по настройке у автора в репозитории), а так же goflymake. При желании посмотрите на golint, go-errcheck и govet. Но на мой взгляд flycheck функциональнее.

Компиляция и запуск

Тут сразу две проблемы:

  • Стандартный модуль compile позволяет настроить только одну команду для режима. А я хочу меню с компиляцией, компиляцией и запуском, запуском тестов и т.п.

  • Я перерыл пол интернета и не нашёл способ обнаружения корня проекта на Go. Если у вас сложная иерархия проекта и открыт файл внутри этого проекта - найти место где нужно грубо говоря запустить build это та ещё задачка.

Первая проблема решается при помощи плагина multi-compile, подробнее читайте в этой статье. А вот вторая в каждом конкретном случае решается особо. Для себя я решил считать корнем проекта ту директорию, где лежит ".git", поскольку любой проект начинаю с "git init". А для экспериментов с языком - использую go-scratch.

Настройка для плагина multi-compile выглядит вот так:

(require 'multi-compile)
(setq multi-compile-alist '(
    (go-mode . (
("go-build" "go build -v"
   (locate-dominating-file buffer-file-name ".git"))
("go-build-and-run" "go build -v && echo 'build finish' && eval ./${PWD##*/}"
   (multi-compile-locate-file-dir ".git"))))
    ))

Да, на первый взгляд - сложно, но если разобраться, то не очень.

Тут написано что для файла открытого в режиме "go-mode" добавить два пункта меню "go-build" и "go-build-and-run" . Первый вызывает консольную команду "go build -v". Второй "go build -v && echo 'build finish' && eval ./${PWD##*/}".

Как рабочая директория у обеих команд устанавливается та, внутри которой лежит ".git", т.е. если у нас открыт файл "~/go/go1/go.go" плагин поищет директорию ".git" внутри "~/go/go1/", если не найдёт, ищет уровнем выше - в "~/go/". Как только нужная директория найдена - она считается рабочей из которой и вызваются скрипты.

Теперь о страшных bash командах: "go build -v" вопросов не вызовет - стандартная компиляция проекта. А вот вторая сложнее:

go build -v && echo 'build finish' && eval ./${PWD##*/}

Конструкция "${PWD##*/}" разворачивается в имя последнего элемента текущей директории. Н-р для "~/go/super_project", получим "super_project". Т.е. для этого примера, команда превращается вот в это:

go build -v && echo 'build finish' && eval ./super_project

Теперь читается проще - компилируем, пишем в консоль, что компиляция завершилась и запускаем получившийся бинарник. Go по умолчанию называет бинарник по имени директории, где находится проект, в нашем случае это "super_project".

Меню с командами компиляции вызывается вот так: M-x multi-compile.


Больше примеров настройки на github.

Тестирование

Я для тестирования пользуюсь goconvey, который умеет самостоятельно обнаруживать изменения в файлах, и перезапускать тесты. Но часто запуск всего набора тестов - лишнее действие, которое сильно грузит процессор и заставляет менять контекст переключаясь в браузер. Поэтому хочется иметь возможность запустить только один тест или все тесты в данном файле оставаясь в Emacs. В этом нам поможет пакет gotest.

Дополнительной настройки он не требует (ну может только горячие клавиши назначить). Умеет он следующее:

  • M-x go-test-current-test - запустить тест внутри которого находится курсор

  • M-x go-test-current-file - запустить тесты внутри текущего файла

  • M-x go-test-current-project - запустить тесты для текущего проекта

  • M-x go-test-current-benchmark - по аналогии - запустить бенчмарк внутри которого находится курсор

  • M-x go-test-current-file-benchmarks - запустить бенчмарки в файле

  • M-x go-test-current-project-benchmarks - запустить бенчмарки в проекте

Локальный Go Playground

Что-бы попробовать как работает та или иная часть Go, обычно пользуются play.golang.org, но у него большой недостаток - он работает в браузере, это не удобно. А локально создавать проект для каждой мелочи лень. Эту нишу покрывает плагин go-scratch. После команды "M-x go-scratch" откроется новый буфер с заготовкой функции main. Компиляция запускается сочетанием клавиш "C-c C-c".

Существует похожий плагин go-playground, но мне он не понравился, т.к. физически создаёт файл на диске, а чистить за ним не хочется.

Отображение структуры кода

Если вас не устраивает imenu, поддержку которого добавляет go-mode, то воспользуйтесь go-direx, который, нужно отдать должное, покажет структуру текущего кода в гораздо более наглядном виде. Как-то настраивать его не нужно, достаточно вызвать "M-x go-direx-switch-to-buffer".

Go guru

Я не могу не упомянуть ещё об одном плагине - go-guru. Он умеет много интересных и полезных вещей, но в рамки статьи они не поместятся. Подробно почитать о них можно в oracle-user-manual, там правда речь идёт о предыдущем названии "go-oracle", сейчас проект переименован в "go-guru", но суть осталась прежней.

Для затравки покажу как найти места откуда вызывается функция. Вызываем:

M-x go-guru-set-scope

И вводим пакет с которым работает (в моём случае это "github.com/ReanGD/go-web-search"). После этого ставим курсор на интересующую функцию и вызываем

M-x go-guru-callers

В отдельном буфере отобразиться список мест откуда функция вызывается. На gif надеюсь понятнее:


Из недостатков - работает медленно, но в любом случае - руками делать подобные вещи дольше. И как я слышал, авторы сейчас серьёзно взялись за проект, надеюсь работать станет удобнее и быстрее.

Tags: emacs
Subscribe
  • Post a new comment

    Error

    default userpic

    Your reply will be screened

    When you submit the form an invisible reCAPTCHA check will be performed.
    You must follow the Privacy Policy and Google Terms of use.
  • 1 comment