20200209のGoに関する記事は6件です。

Pythonのbisect_leftとbisect_rightをGo言語でも使いたい

Python3にはbisectというモジュールがあり、この中にはbisect_leftbisect_rightという関数が用意されています。bisect_leftはソート済みの配列を二分探索により探索し、挿入点のうちもっとも左側のものを戻り値とします。bisect_rightはその逆で、挿入点のうち、もっとも右側が戻り値となります。

import bisect

digits = [111, 222, 333, 333, 333, 444, 555]
bisect.bisect_left(digits, 333)  #=> 2
bisect.bisect_right(digits, 333) #=> 5

Go言語でbisect_leftbisect_rightと同じようなことをしたい場合、すなわち、二分探索でもっとも右側の挿入点を探したい、もっとも左側の挿入点を探したい場合、sortパッケージのSearchを利用し、それぞれ次のように実装します。

digits := []int{111, 222, 333, 333, 333, 444, 555}
sort.Search(len(digits), func(i int) bool { return digits[i] >= 333 }) //=> 2
sort.Search(len(digits), func(i int) bool { return digits[i] > 333 })  //=> 5

つまりbisect_leftの場合は不等号で比較、bisect_rightの場合は等号付き不等号で比較すればよいわけです。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

Goでシグナルを監視する君を書いた

tl;dt

https://github.com/nozo-moto/signal_kansitai

目的

ちょっとした調査、アプリケーションにどんなシグナルが送られてくるか知りたかった

方法

Go言語は以下のようにすると作成したgoroutineにシグナルがあるとハンドリングできます。

c := make(chan os.Signal)
signal.Notify(c)
go func() {
    for {
        s := <-c
        log.Println("signal :", s)
        }
    }
}()

よしなにロギング
標準出力とログファイルに書き出したかったのでio.MultiWriterを利用

logfile, err := os.OpenFile("./signal.log", os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0666)
if err != nil {
    panic("cannnot create log :" + err.Error())
}
defer logfile.Close()
log.SetOutput(io.MultiWriter(logfile, os.Stdout))
log.SetFlags(log.Ldate | log.Ltime)

こんな感じでシグナルをハンドリングして流してくれる

% tail -f signal.log
2020/02/09 21:48:01 alive :
2020/02/09 21:49:01 alive :
2020/02/09 21:50:01 alive :
2020/02/09 21:50:33 signal : hangup false

SIGTERMが送られてきたら落とすようにしました。60秒に一回生存確認をしています。
また、SIGKILLとかのハンドリングできない系はハンドリングできずに死にます。

知見

SIGWINCHというターミナルのWindowサイズの変更のシグナルがあることを知った。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

GolangをFastCGIとしてApacheと共に動かす

tl;dt

Go言語のnet/http/fcgiを使ってGoをCGIとしてApacheで動かしました。

動かす

git clone https://github.com/nozo-moto/golang-fastcgi-try
cd golang-fastcgi-try
make docker-build
dokcer-compose up

確認

$ curl localhost:8888
{"message":"pong"}

net/http/fcgi

Go言語にはnet/http/fcgiというFastCGI protocolが実装されたパッケージがあります。

これは以下のようにhttp.Handlerをラップしてあげて使えます。今回はginを使っています。

package main

import (
    "net"
    "net/http/fcgi"

    "github.com/gin-gonic/gin"
)

func main() {
    var err error
    r := gin.Default()
    r.GET("/ping", func(c *gin.Context) {
        c.JSON(200, gin.H{
            "message": "pong",
        })
    })
    l, err := net.Listen("tcp", ":8080")
    if err != nil {
        panic(err)
    }
    if err := fcgi.Serve(l, r); err != nil {
        panic(err)
    }
}

動かす

以下のようなdocker-composeを用意します
以下ではてきとうなコンテナに上のGoのコードのバイナリを入れて実行しています。

version: '3.3'
services:
  www:
    image: httpd:2.4.41
    ports:
        - "8888:80"
    depends_on:
        - fcgi
    volumes:
        - ./docker/apache/conf/fcgi.conf:/usr/local/apache2/conf/extra/fcgi.conf
    command: bash -c "echo 'Include conf/extra/fcgi.conf' >> /usr/local/apache2/conf/httpd.conf && httpd-foreground"
  fcgi:
    image: nozomi0966/fastcgi-golang-try

fcgi.conf

ApacheでfastCGIを実行するための設定ファイルを書きます
上記のdocker-compose.ymlで./docker/apache/conf/fcgi.confに書いたのを読んでいます。
Apache2.4以降からmod_proxy_fcgiというモジュールが入っているのでそれを使います。
docker-composeでfcgiというホスト名で引けるように動かしているので以下のようにfcgi:8080でアクセスします。

LoadModule proxy_module modules/mod_proxy.so
LoadModule proxy_fcgi_module modules/mod_proxy_fcgi.so
SetHandler "proxy:fcgi://fcgi:8080/"

以下を実行してApacheとFastCGIを動かします。

docker-compose up

以下のようなレスポンスが返ってくれば動いてるのが確認できます。

$ curl localhost:8888
{"message":"pong"}

以上

以下のリポジトリにコードを載せています
https://github.com/nozo-moto/golang-fastcgi-try

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

AtCoder 過去問精選10問 をGo言語で解いてみた

はじめに

何番煎じかわかりませんが、Go言語の基本文法を練習するのによさそうだったので、AtCoder に登録したら次にやること ~ これだけ解けば十分闘える!過去問精選 10 問 ~を解いてみました。
普段はPythonしか書いていないので、書き方の違いにも軽く触れてみました。

実際に解いてみる場合は、AtCoder Beginners Selectionに問題がまとめられているので便利です。

問題

0. PracticeA - Welcome to AtCoder

標準入出力の練習です。
標準出力は特に問題ないですが、標準入力については、先に変数を宣言してから、ポインタを使って値を更新するような形になります。

ひとまずは、fmt.Scan()fmt.Println()が使えれば問題ないと思います。
fmt.Scan()は標準入力をスペースだけでなく改行でも区切るので注意しましょう。

解答例

package main

import "fmt"

func main() {
    var (
        a, b, c int
        s       string
    )
    fmt.Scan(&a, &b, &c, &s)

    fmt.Println(a+b+c, s)
}

1. ABC086A - Product

条件分岐(if文)の練習です。
また、$a \times b$ が偶数になるのは、$a, b$ のいずれかが偶数のときです。

解答例

package main

import "fmt"

func main() {
    var a, b int
    fmt.Scan(&a, &b)

    if a%2 == 0 || b%2 == 0 {
        fmt.Println("Even")
    } else {
        fmt.Println("Odd")
    }
}

2. ABC081A - Placing Marbles

各桁は0か1しか取らないので、それぞれが1と等しいか比較すればよいです。
比較回数が3回で固定のため、for文を使わなくても十分いけます。

ただし、Go言語では文字列からstring[index]の形で1文字だけ抜き出すと文字列ではなくバイトになるため、stringの"1"ではなくruneの'1'でないと正しく比較できないことに注意してください。

解答例

package main

import (
    "fmt"
)

func main() {
    var s string
    fmt.Scan(&s)

    ans := 0
    if s[0] == '1' {
        ans++
    }
    if s[1] == '1' {
        ans++
    }
    if s[2] == '1' {
        ans++
    }

    fmt.Println(ans)
}

別解

以下のようにstrconv.Atoi()を使用して数値型に変換する方法もあります。数字が0から9までの値を取る場合などは、条件式を10個書くのは面倒なのでこちらの方がよいでしょう。
ここで、strconv.Atoi()は2つ目の戻り値としてエラーを返しますが、今回は使わないので_で受けておきましょう。

また、GoではPythonとは違ってint()で文字列を整数に変換することはできないので注意しましょう。

package main

import (
    "fmt"
    "strconv"
)

func main() {
    var s string
    fmt.Scan(&s)

    ans := 0
    tmp, _ := strconv.Atoi(string(s[0]))
    ans += tmp
    tmp, _ = strconv.Atoi(string(s[1]))
    ans += tmp
    tmp, _ = strconv.Atoi(string(s[2]))
    ans += tmp

    fmt.Println(ans)
}

3. ABC081B - Shift only

2で割り切れない数字が出てくるまで繰り返し割ります。繰り返し回数がわからないのでfor文の出番です。
Goにはwhile文やfor each文がありませんが、for文で代用することが可能です。

また、ここでは数値の配列を管理する必要があるので、スライスを使用してみます。
スライスはmake()を使って生成します。

解答例

package main

import (
    "fmt"
)

func main() {
    var n int
    fmt.Scan(&n)
    nums := make([]int, n)
    // Pythonの for i in range(n): に相当
    for i := 0; i < n; i++ {
        fmt.Scan(&nums[i])
    }

    var ans int
    ok := true
    // Pythonの while ok: に相当
    for ok {
        // Pythonの for i, num in enumerate(nums): に相当
        for i, num := range nums {
            if num%2 != 0 {
                ok = false
                break
            }
            nums[i] /= 2
        }
        if ok {
            ans++
        }
    }

    fmt.Println(ans)
}

4. ABC087B - Coins

多重ループの問題です。
for文を3つネストさせれば簡単に解ける問題ですが、1つ減らして計算量を削減しています。

また、Goでは、if文を書くときに条件式の前に変数を宣言することができます。ここで宣言した変数はスコープがif文の中に制限されるため、条件式の中で使いたいが、if文の外では使わない変数を宣言するのに便利です。

解答例

package main

import (
    "fmt"
)

func main() {
    var a, b, c, x, ans int
    fmt.Scan(&a, &b, &c, &x)

    for i := 0; i <= a; i++ {
        for j := 0; j <= b; j++ {
            if k := (x - 500*i - 100*j) / 50; 0 <= k && k <= c {
                ans++
            }
        }
    }

    fmt.Println(ans)
}

5. ABC083B - Some Sums

ポイントは10進数の整数の取り扱いです。
「10で割った余りを取り出す」→「10で割る」を繰り返せば各桁の値を取り出すことができます。

今回は、この処理をsumOfDigits()関数として定義しました。
Goでは、戻り値の型を指定するときに、変数名も併せて定義しておくことができます。この場合、関数内での変数宣言を省略できます。また、returnの対象を明示しない場合、定義しておいた変数をreturnします。

解答例

package main

import (
    "fmt"
)

func sumOfDigits(n int) (r int) {
    for n > 0 {
        r += n % 10
        n /= 10
    }
    return
}

func main() {
    var n, a, b, ans int
    fmt.Scan(&n, &a, &b)

    for i := 1; i <= n; i++ {
        if s := sumOfDigits(i); a <= s && s <= b {
            ans += i
        }
    }

    fmt.Println(ans)
}

6. ABC088B - Card Game for Two

ソートの問題です。
Goでは、ソートにはsortパッケージを使用します。
int型のスライスをソートする場合、sort.Ints(slice)またはsort.Sort(sort.IntSlice(n))とします。
降順にソートしたい場合は、sort.Sort(sort.Reverse(sort.IntSlice(n)))とします。

降順ソートさえできてしまえば、後は貪欲法でAliceとBobに交互に得点を割り振ればOKです。

解答例

package main

import (
    "fmt"
    "sort"
)

func main() {
    var n int
    fmt.Scan(&n)

    nums := make([]int, n)
    for i := range nums {
        fmt.Scan(&nums[i])
    }

    var alice, bob int
    sort.Sort(sort.Reverse(sort.IntSlice(nums)))
    for i, num := range nums {
        if i%2 == 0 {
            alice += num
        } else {
            bob += num
        }
    }
    fmt.Println(alice - bob)
}

7. ABC085B - Kagami Mochi

与えられた餅の直径のユニークな値を出せばよいです。

Pythonであればset型を使えば一発でしたが、Goには集合を表す型はないのでmap型で代用します。
Goのmap型は、スライスと同様にmakeで生成します。

解答例

package main

import (
    "fmt"
)

func main() {
    var n int
    fmt.Scan(&n)
    m := make(map[int]bool)
    for i := 0; i < n; i++ {
        var d int
        fmt.Scan(&d)
        m[d] = true
    }

    fmt.Println(len(m))
}

8. ABC085C - Otoshidama

4. と似た問題ですが、3重ループにすると時間が厳しいので2重ループで解きます。
文法的には特筆すべき点はありません。

解答例

package main

import "fmt"

func main() {
    var n, y int
    fmt.Scan(&n, &y)

    for i := 0; i <= n; i++ {
        if i*10000 > y {
            break
        }
        for j := 0; j <= n-i; j++ {
            k := n - i - j
            if i*10000+j*5000+k*1000 == y {
                fmt.Println(i, j, k)
                return
            }
        }
    }
    fmt.Println(-1, -1, -1)
}

9. ABC049C - 白昼夢

いろいろやり方はありますが、正規表現でやるのが簡単かと思います。
Go言語ではregexpパッケージを使います。
※ どうやら、Go言語の正規表現はあまり処理が早くないらしいです。

解答例

package main

import (
    "fmt"
    "regexp"
)

func main() {
    var s string
    fmt.Scan(&s)

    r := regexp.MustCompile(`^(dream(er)?|erase(r)?)*$`)
    if r.MatchString(s) {
        fmt.Println("YES")
    } else {
        fmt.Println("NO")
    }
}

10. ABC086C - Traveling

$t_0 = x_0 = y_0 = 0, ~ dt_i = dt_{i+1} - dt_i, ~ dx_i = |x_{i+1} - x_i|, ~ dy_i = |y_{i+1} - y_i|$ とすると、
旅行プランを実行できるのは、任意の $i ~ (0 \leq i < N)$ に対して、以下の2条件をみたすときです。

  • $dx_i + dy_i \leq dt_i$
  • $dx_i + dy_i \equiv dt_i \pmod 2$

絶対値の計算はmath.Abs()がありますが、int型の引数で使いたいので今回は自作しました。

解答例

package main

import (
    "fmt"
)

func abs(n int) int {
    if n < 0 {
        return -n
    }
    return n
}

func main() {
    var n int
    fmt.Scan(&n)
    t := make([]int, n+1)
    x := make([]int, n+1)
    y := make([]int, n+1)
    for i := 1; i <= n; i++ {
        fmt.Scan(&t[i], &x[i], &y[i])
    }

    ans := "Yes"
    for i := 0; i < n; i++ {
        dt := t[i+1] - t[i]
        dxy := abs(x[i+1]-x[i]) + abs(y[i+1]-y[i])
        if dt < dxy || dt%2 != dxy%2 {
            ans = "No"
            break
        }
    }

    fmt.Println(ans)
}

参考

まとめ

普段はずっとPythonで書いているので、困惑する部分もありましたがよい勉強になりました。
deferpanic, goなど、競技プログラミングでは使わなそうな機能については別で勉強したいと思います。

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

【go + gin + gorm】webアプリにログイン機能を追加してみる

【Go+Gin+Gorm】初心者だから超簡単webサービス作ってみるの続きです。

今回はgithubに上げておきました。
https://github.com/daichiiyamada/mytweet

ここではユーザー登録とログイン画面を作ります。
セッションはやりません(おそらく次回)。
ログイン画面からusernameとpasswordを入力してDBに存在していたらトップ画面にリダイレクトされるようになっています。

外部ライブラリのインストール

続きの方はターミナルを開いて以下を実行してください。
ここから始める方はmain.gocrypto/crypto.goのimportを見てgo runしてください。

// gormConnect()内で.envファイル(環境変数定義)から定数を取得するときに使います
go get github.com/joho/godotenv
// Usersテーブルにパスワードをそのまま保存するとセキュリティ的に危ないので、これを使って暗号化して保存します。
go get golang.org/x/crypto/bcrypt

.envファイル(環境変数定義)を使用する

先ほどインストールしたgodotenvは、go言語で.envファイルを使うためのライブラリです。
ハードコーディングを避けるため、今回は使ってみようと思います。
.envファイルの中に定数を記述して、コード内でその定数を使うことができます。使い方もクソもないかもですが、こちらを参照するとわかりやすいかもです。
main.goと同じ階層に.envファイルを作り、前回の続きの方は以下のように記述してください。
これらはMySQLのDB名だったり、ユーザー名だったりを記載しています。

.env
mytweet_DBMS=mysql
mytweet_USER=test
mytweet_PASS=12345678
mytweet_DBNAME=test

usernameをユニークに設定する

ログインを実装するにあたって、ログインフォームから受け取ったusernameをデータベースで検索して一意なユーザーを取得したいので、usernameはユニークである必要があります。
なのでmain.goファイルのUser構造体を変更しました。
gorm:"unique;not null"の部分です。
これによってアプリ側で、重複するUsernameを新たに登録しようとしてもデータベース側で弾けます。

db.AutoMigrate()はテーブルや不足しているカラムとインデックスのみ生成します。データ保護のため、既存のカラム型の変更や未使用のカラムの削除はしないので、前回と同じテーブルを使う方、申し訳ないんですが一度Usersテーブルをtruncateしてください。ここら辺railsはActive Recordがやってくれますよね。
AutoMigrateについて詳しくは公式リファレンスを参照ください。
プロダクト向きのマイグレーションツールが他にあるそうなので気になる方は調べてみてください。今回は規模が大きくないので、これでいきます。
Go言語で使えるmigrationライブラリ

ちなみに、プロダクト開発でgormを使おうと考えている方はこちらの記事を読んでみるといいかもしれません。
Go言語のGormを実践投入する時に最低限知っておくべきことのまとめ【ORM】

main.go
// User モデルの宣言
type User struct {
    gorm.Model
    Username string `form:"username" binding:"required" gorm:"unique;not null"`
    Password string `form:"password" binding:"required"`
}

ユーザー登録処理

ユーザー登録でアプリ側にさせることは、フォーム画面からユーザー名とパスワードを受け取ってDBに登録することです。

URLlocalhost:8080/signupでユーザー登録画面にいきます。トップ画面上から飛べるようにするのを忘れてました(笑)。
この画面を出すだけなら特に関数は必要ありません。
登録ボタンを押すとsignup.htmlフォーム内のaction="/signup"/signupにPOSTを投げるようにしています。

User型のformで構造体を定義し、Bind関数を使って、構造体で定義された内容と違ったデータが来てないか把握することができます。
気になる方は、GinでBindingが物珍しかったので他のフレームワークも調べてみたを読んでみるといいと思います。

フォームの内容は変数c内に格納されていて、PostForm()を使って値を取り出します。

main.go
// ユーザー登録画面
    router.GET("/signup", func(c *gin.Context) {

        c.HTML(200, "signup.html", gin.H{})
    })

    // ユーザー登録
    router.POST("/signup", func(c *gin.Context) {
        var form User
        // バリデーション処理
        if err := c.Bind(&form); err != nil {
            c.HTML(http.StatusBadRequest, "signup.html", gin.H{"err": err})
            c.Abort()
        } else {
            username := c.PostForm("username")
            password := c.PostForm("password")
            // 登録ユーザーが重複していた場合にはじく処理
            if err := createUser(username, password); err != nil {
                c.HTML(http.StatusBadRequest, "signup.html", gin.H{"err": err})
            }
            c.Redirect(302, "/")
        }
    })

取り出したusernameとpasswordをcreateUser関数に引数で渡します。
/crypto/crypto.go内のPasswordEncrypt()関数を使ってパスワードを暗号化します。
暗号化されたパスワードとユーザーネームをUsersテーブルに保存します。

package内の関数は、先頭文字を大文字にするとpublic関数になり、小文字にするとprivate関数になります。
PasswordEncrypt()関数は先頭が大文字のPなので、public関数ですね。

ユーザーが重複していたりして登録できなかったときにリダイレクトしたいので、GetErrors()でエラーを取得し、returnできるようにしています。
GORMのエラーハンドリングに関する記述

main.go
// ユーザー登録処理
func createUser(username string, password string) []error {
    passwordEncrypt, _ := crypto.PasswordEncrypt(password)
    db := gormConnect()
    defer db.Close()
    // Insert処理
    if err := db.Create(&User{Username: username, Password: passwordEncrypt}).GetErrors(); err != nil {
        return err
    }
    return nil

}

ログイン処理

ログイン処理でアプリ側でさせることは、ログインフォームから受け取ったユーザー名とパスワードがDBに同じく保存されているか探すことです。
流れとしては、

ログインフォームからユーザー名とパスワードを受け取る
↓
ユーザー名をもとにUsersテーブルからユーザーレコードを取得する
↓
ログインフォームから受け取ったパスワードとDBから取得したユーザーレコードのパスワードと比較
↓
トップ画面へリダイレクトまたはログイン画面に戻る

URLlocalhost:8080/loginでログイン画面にいきます。これまたトップ画面上から飛べるようにするのを忘れてました(笑)。
この画面を出すだけなら特に関数は必要ありません。
登録ボタンを押すとlogin.htmlフォーム内のaction="/login"/loginにPOSTを投げるようにしています。

POSTで受け取ったユーザー名の値をgetUser()に引数で入れ、DBからユーザーレコードを取得しています。取得したユーザーレコードはUser型として取得しているので、.Passwordでパスワードを取得できます。
パスワードの比較は、CompareHashAndPassword()の引数にdbPasswordformPasswordを入れることで比較することができます。この関数は、cryptoディレクトリのcrypto.goに定義されています。この関数のreturnはerror型なので、エラー内容があればif文で引っかかるようになってます。

main.go
// ユーザーログイン画面
    router.GET("/login", func(c *gin.Context) {

        c.HTML(200, "login.html", gin.H{})
    })

    // ユーザーログイン
    router.POST("/login", func(c *gin.Context) {

        // DBから取得したユーザーパスワード(Hash)
        dbPassword := getUser(c.PostForm("username")).Password
        log.Println(dbPassword)
        // フォームから取得したユーザーパスワード
        formPassword := c.PostForm("password")

        // ユーザーパスワードの比較
        if err := crypto.CompareHashAndPassword(dbPassword, formPassword); err != nil {
            log.Println("ログインできませんでした")
            c.HTML(http.StatusBadRequest, "login.html", gin.H{"err": err})
            c.Abort()
        } else {
            log.Println("ログインできました")
            c.Redirect(302, "/")
        }
    })

データベースからwhere句を使ってユーザーを1件取得する際に使ったのが、getUser内のdb.First()です。
db.Where()でも代用できると思われます。他にもdbにまつわる様々な関数があるので、詳しく知りたい方は以下を読んでみるといいかと思います。
参照1
参照2

db.First(&user, "username = ?", username)
↓
SELECT * FROM users WHERE username = "jinzhu";
main.go
// ユーザーを一件取得
func getUser(username string) User {
    db := gormConnect()
    var user User
    db.First(&user, "username = ?", username)
    db.Close()
    return user
}

起動

起動させていろいろ遊んでみましょう。
ターミナルを付けてmytweetディレクトリ内でgo run main.goをしてください。
localhost:8080/signupでユーザー登録
localhost:8080/loginでログイン
ですよ。
トップ画面から飛べるようにするの忘れてごめんなさい。

MySQLを別タブで起動してちゃんとユーザー登録できてるか、ログインできてるか確かめてみましょう。
ちなみに、SQL文って書き方よく忘れるよね。

// mysql起動
$ mysql -uroot -p

// testデータベースを選択
$ use test;

// usersテーブル全レコード表示
$ select * from users;

各自print文(log.Println())を使ってパスワードがちゃんとハッシュ化されてるかとか、ユーザー名がちゃんと取得できてるかとかみてみると勉強になるかと思います。

最後に

今回は、前回のコードにユーザー登録とログイン機能を追加してみました。
次回は、デプロイしてみたり、セッション機能を追加してみたりしたいと思ってます。

今回のコードはこちらから。
https://github.com/daichiiyamada/mytweet

cryptoの部分はこちらのサイトのコードを流用させていただきました。
Webアプリ初心者がGo言語でサーバサイド(2. パスワード認証機能の実装)

  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む

VSCode で Go の Language Server である gopls を有効にする

gopls とは

gopls(Go Please)は Go が公式サポートしている Language Server です。現在開発が活発に進んでおり 2020年2月9日時点 での最新バージョンは v0.3.1 となっています。

VSCode の Go拡張 における Language Server について

元々 VSCode の Go拡張である microsoft/vscode-go のデフォルトの Language Server は sourcegraph/go-langserver だったのですが、go-langserver の内部で利用されていた nsf/gocode の開発終了を受け、こちら で話し合いがなされた末に 2019年3月に gopls へ 変更 となりました。

なお、2020年2月9日時点での vscode-go の最新バージョン v0.13.0 では、Language Server の使用が デフォルト無効化 となっているので使用したい場合には有効化する必要があります。

Language Server の有効化方法

マニュアル に従い VSCode で以下の作業を実施します。
VSCode に vscode-go がインストールされていない方はインストールを終えた後で実施してください。

1. Language Server の有効化

Code > Preferences > Settings (cmd+,) を開き go.useLanguageServer を検索して有効化する。
スクリーンショット 2020-02-09 15.15.19.png

2. gopls のインストール

View > Command Palette (cmd+shift+P) を開き Go: Install/Update Tools を検索した後で gopls を選択してインストールする。
スクリーンショット 2020-02-09 15.16.15.png
スクリーンショット 2020-02-09 15.16.29.png
スクリーンショット 2020-02-09 15.16.48.png

gopls v0.3.1 がインストールされました。

$ gopls version
golang.org/x/tools/gopls v0.3.1
    golang.org/x/tools/gopls@v0.3.1 h1:yNTWrf4gc4Or0UecjOas5pzOa3BL0WDDyKDV4Wz5VaM=

3. 任意の設定を適用

こちらは任意なのでスキップしても問題ないです。

View > Command Palette (cmd+shift+P) を開き Preferences: Open Settings (JSON) を検索して settings.json に任意の設定を適用する。こちら を参考にして以下の設定を適用した例となります。

// For gopls
"go.useLanguageServer": true,
"[go]": {
    "editor.formatOnSave": true,
    "editor.codeActionsOnSave": {
        "source.organizeImports": true,
    },
    // Optional: Disable snippets, as they conflict with completion ranking.
    "editor.snippetSuggestions": "none",
},
// Global settings for gopls
// https://github.com/golang/tools/blob/master/gopls/doc/settings.md
"gopls": {
    // === Officially supported Settings ===

    // This controls the information that appears in the hover text.
    "hoverKind": "SynopsisDocumentation",
    // If true, then completion responses may contain placeholders for function parameters or struct fields.
    "usePlaceholders": true,
    // This controls where points documentation for given package in `textDocument/documentLink`.
    "linkTarget": "pkg.go.dev",

    // === Experimental Settings ===

    // If true, it enables the use of the staticcheck.io analyzers.
    // Warning: This will significantly increase memory usage.
    "staticcheck": false,
    // If false, indicates that the user does not want documentation with completion results.
    "completionDocumentation": true,
    // If true, the completion engine is allowed to make suggestions for packages that you do not currently import.
    "completeUnimported": true,
    // If true, this turns on the ability to return completions from deep inside relevant entities, rather than just the locally accessible ones.
    "deepCompletion": true
}

スクリーンショット 2020-02-09 15.37.36.png
スクリーンショット 2020-02-09 15.39.08.png

4. 設定の適用

設定を適用するために VSCode を Reload する。これにより Language Server が有効化されます。開発が捗りそうです。

おまけ: Go のコード補完ツールの歴史

Big Sky :: gocode やめます(そして Language Server へ) がとてもわかりやすく非常に参考になったので、理解するがてら時系列を整理してみました。現時点では gopls を利用していくのが良さそうです。

  1. Go 1.10 以前は gocode がデファクトスタンダード
  2. Go 1.10 に追加されたビルドキャッシュの関係で gocode の開発が終了
  3. それと同時に gocode を利用していた go-langserver の開発にも影響が出た
  4. これらの状況を受けて gocode の fork で mdempsky/gocodestamblerre/gocode が登場
  5. Go の公式 Language Server として golsp が登場(stamblerre/gocode の人が開発を担当)
  6. 別の Go の Language Server として saibing/bingo が登場
  7. golsp 開発者が bingo 開発者にコラボレーションしようと 打診 する
  8. golsp が gopls(Go Please) に rename される
  9. コラボレーションが了承されて golang/tools を fork し bingo の機能を gopls に移植した saibing/tools が登場
  • このエントリーをはてなブックマークに追加
  • Qiitaで続きを読む