DockerでCentOS7を動かしてvimでC/C++の自動補完を体験する

# はじめに
Windows10 Home(64bit版)でDockerを使っていて、CentOS7のvimでC/C++のコード補完を体験したい。本記事は、そんな人のために書かれています。

本記事では、ランゲージサーバの簡易導入ツールである「vim-lsp-settings」を使って「vim-lsp」の自動補完機能をvimに導入します。

https://qiita.com/mattn/items/e62b9f16bc487a271a7f

「vim-lsp-settings」を使うことによって、PythonだろうがC++だろうが、特定のプログラミング言語のファイルを開いたら拡張が提案されるようになります。これにより、面倒だったランゲージサーバーのインストールがあっさりと終わります。

対応しているプログラミング言語については、上述の「vim-lsp-settings」の記事([リンク](https://qiita.com/mattn/items/e62b9f16bc487a271a7f))に一覧表が載っていますが、Python、Javascript、Rust、Go、C/C++、C#、Javaなど幅広く対応していることが分かります。

CentOS7に「vim-lsp-settings」をインストールしてC/C++で補完が効くようになるまで持っていくには色々な作業が必要です。その作業を出来るだけ自動化して、とりあえず動くところを見たいという方のために本記事は書かれています。

Linuxのディストリビューションやバージョンによっては、もっと簡単に入れられるようですから、CentOS7にこだわりがない方は別の方法を探した方が良いかもしれません。


# 動作環境
次のソフトウェアはすでにインストールされているものとします。

- Windows10 Homeの64bit版
- Docker Desktop for Windows


# 必要スキル
次のソフトウェアを日常的に使っているものとします。

- Windows10 Homeの64bit版
- Docker Desktop for Windows
- CentOS
- vi / vim (※)

※ viかvimのどちらかというorの意味で/を使っています。

# 所要時間
後述するように、インストール自体は自動で進みますから、PCの前でずっと待機する必要はありません。しかし、PCの性能によっては2時間くらい何か別の作業をしながら待つことになります。(CentOS7でvim-lspを使うため、gccやcmakeやclangdをソースからビルドする必要があるからです。18コアのうち15コアを指定してビルドに30分弱かかりましたから、単純計算で3コアだと150分弱かかるのではないかと思われます。)

# 環境構築
自動で環境構築を終わらせるため、Dockerfileを用意しました。

## イメージの構築
空のフォルダを新規作成して、次のDockerfileを置いて下さい。拡張子はつけません。

```dockerfile:Dockerfile
FROM centos:centos7

# コア数を入力
ARG NUMBER_OF_CORES=1

# パッケージマネージャの更新
RUN yum -y update && yum clean all

# ユーザ設定
ARG USERNAME=tony
ARG GROUPNAME=stark
ARG UID=110
ARG GID=110
ARG PASSWORD=ironman2008!
ARG ROOT=root
ARG ROOT_PASSWORD=avengers2012!
RUN groupadd -g $GID $GROUPNAME && \
    useradd -m -s /bin/bash -u $UID -g $GID -G wheel $USERNAME && \
    echo $USERNAME:$PASSWORD | chpasswd && \
    echo $ROOT:$ROOT_PASSWORD | chpasswd

# ホームディレクトリの構築
USER $USERNAME
WORKDIR /home/$USERNAME/

# ダウンロードしたソースファイルを保存するための
# ディレクトリを作成
ARG SRC_DIR=/home/$USERNAME/src
RUN mkdir $SRC_DIR
    
# 必要なソフトウェアのビルド

# ビルドに必要なパッケージのインストール
USER root
RUN yum -y install unzip wget gcc gcc-c++ make cmake bzip2 openssl-devel git

## GLIBC_2.81のインストール
USER $USERNAME
RUN cd $SRC_DIR && \
    wget https://ftp.gnu.org/gnu/glibc/glibc-2.18.tar.gz && \
    tar xvfz glibc-2.18.tar.gz && \
    cd glibc-2.18 && \
    mkdir build && \
    cd build && \
    ../configure --prefix=/usr --disable-profile --enable-add-ons --with-headers=/usr/include --with-binutils=/usr/bin && \
    make -j $NUMBER_OF_CORES
USER root
RUN cd $SRC_DIR/glibc-2.18/build && \
    make install

## clangdのインストール

### gccのソースファイルをダウンロード、コンパイル、インストール
USER $USERNAME
RUN cd $SRC_DIR && \
    wget http://ftp.mirrorservice.org/sites/sourceware.org/pub/gcc/releases/gcc-7.3.0/gcc-7.3.0.tar.gz && \
    tar xvfz gcc-7.3.0.tar.gz && \
    cd gcc-7.3.0 && \
    ./contrib/download_prerequisites && \
    ./configure --prefix=/opt/gcc --disable-multilib --enable-languages=c,c++ && \
    make -j $NUMBER_OF_CORES
USER root
RUN cd $SRC_DIR/gcc-7.3.0 && \
    make install
USER $USERNAME

### cmakeのソースファイルをダウンロード、コンパイル、インストール
USER $USERNAME
RUN cd $SRC_DIR && \
    wget https://github.com/Kitware/CMake/releases/download/v3.17.3/cmake-3.17.3.tar.gz && \
    tar xvfz cmake-3.17.3.tar.gz && \
    cd cmake-3.17.3 && \
    ./bootstrap --prefix=/opt/cmake && \
    make -j $NUMBER_OF_CORES
USER root
RUN cd $SRC_DIR/cmake-3.17.3 && \
    make install

### clangdのソースファイルをダウンロード、コンパイル、インストール
USER root
RUN yum -y install python3
USER $USERNAME
RUN export PATH=/opt/gcc/bin/:/opt/cmake/bin/:$PATH && \
    cd $SRC_DIR && \
    wget https://github.com/llvm/llvm-project/archive/llvmorg-12.0.0.tar.gz && \
    tar xvfz llvmorg-12.0.0.tar.gz && \
    cd llvm-project-llvmorg-12.0.0 && \
    mkdir build && \
    cd build && \
    CC=/opt/gcc/bin/gcc CXX=/opt/gcc/bin/g++  /opt/cmake/bin/cmake ../llvm -DCMAKE_CXX_LINK_FLAGS="-Wl,-rpath,/opt/gcc/lib64 -L/opt/gcc/lib64" -DLLVM_ENABLE_PROJECTS="clang;clang-tools-extra" -DLLVM_TARGETS_TO_BUILD="X86" -DCMAKE_BUILD_TYPE=Release -DCMAKE_INSTALL_PREFIX=/opt/clang && \
    make -j $NUMBER_OF_CORES
USER root
RUN cd $SRC_DIR/llvm-project-llvmorg-12.0.0/build && \
    make install
    
## vimに必要なソフトウェアのインストール
    
### libatomicのインストール
USER root
RUN yum -y install libatomic

### vimのインストール
USER root
RUN yum -y install ncurses-devel
USER $USERNAME
RUN cd $SRC_DIR && \
    git clone https://github.com/vim/vim.git && \
    cd vim && \
    make -j $NUMBER_OF_CORES
USER root
RUN cd $SRC_DIR/vim && \
    make install
    
### vim-plugのダウンロード
USER $USERNAME
WORKDIR /home/$USERNAME/
RUN curl -fLo /home/$USERNAME/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

### vim-plugの設定ファイルを準備
USER $USERNAME
ARG VIMRC=/home/$USERNAME/.vimrc
RUN echo "syntax enable" >> $VIMRC
RUN echo "colorscheme desert" >> $VIMRC
RUN echo "call plug#begin('~/.vim/plugged')" >> $VIMRC
RUN echo "    Plug 'prabirshrestha/vim-lsp'" >> $VIMRC
RUN echo "    Plug 'mattn/vim-lsp-settings'" >> $VIMRC
RUN echo "    Plug 'prabirshrestha/asyncomplete.vim'" >> $VIMRC
RUN echo "    Plug 'prabirshrestha/asyncomplete-lsp.vim'" >> $VIMRC
RUN echo "call plug#end()" >> $VIMRC
RUN echo "" >> $VIMRC
RUN echo ":set encoding=utf-8" >> $VIMRC
RUN echo ":set fileencodings=iso-2022-jp,euc-jp,sjis,utf-8" >> $VIMRC
RUN echo ":set fileformats=unix,dos,mac" >> $VIMRC


## Dockerイメージのビルト
## docker build -t centos7_vim_lsp .
## Dockerコンテナの作成と起動
## docker run  --name centos7_vim_lsp -u 110:110 -it centos7_vim_lsp /bin/bash
## vimのvim-plugでプラグインインストール
## :PlugInstall
## 
```
高性能のPCを持っている人は、4行目のコア数(NUMBER_OF_CORES)を増やすとインストールが早く終わります。CPUのコア数から1を引いた数が良いと言われています。ここでのコア数の設定値が"make -j 1"の1のようにmakeに渡されます。

よく分からない人はコア数を1にしたまま「ARG NUMBER_OF_CORES=1」で先に進むか、次の記事を読んでコア数を調べて1を引いた数を設定してから先に進んでください。

https://hayakute.kantan-sakusaku.com/?p=1671

例えば、コア数が4なら、Dockerfileの4行目を「ARG NUMBER_OF_CORES=3」というように変えます。

Dockerfileでコア数を設定したら、いつものようにイメージを構築して下さい。例えば、コマンドプロンプトを立ち上げたら、Dockerfileを置いたフォルダに「cd」コマンドで移動して、次のように「docker build」コマンドでイメージを構築します。

```terminal
docker build -t centos7_vim_lsp .
```
「-t」は作成するイメージ名を指定するオプションです。ここでは「centos7_vim_lsp」という名前になるように新しいイメージ名を指定しています。また、最後の「.」は「コンテクスト」と呼ばれる構築時に必要なファイルが置かれているフォルダのパスです。ここではDockerfileが置かれたフォルダを相対パスで指定しています。

「docker build」コマンドについては、公式サイトに詳細が載っています。

https://docs.docker.jp/engine/reference/commandline/build.html

「docker build」コマンドを打つと、PCの性能によっては2時間くらい待つことになります。PCの前で待つ必要はなく、何か別の作業をしながら完了するのを待ちましょう。完了したら、次に進んでください。


# 自動補完の体験

さぁ、これで環境構築は完了です。

コンテナを実行して、vimにプラグインをインストールして、さっそく自動補完に挑戦してみましょう。

## コンテナの作成と起動

Dockerfileからイメージを作成したら、いつものようにコンテナを起動してください。先ほどDockerfileを置いたフォルダで次のように「docker run」コマンドを打ちます。このコマンドにより、イメージからコンテナが作成され、起動します。

```terminal
docker run  --name centos7_vim_lsp -u 110 -it centos7_vim_lsp /bin/bash
```

1番目のオプションである「--name」では、コンテナ名を指定しています。ここでは、イメージ名と同じ「centos7_vim_lsp」を指定しています。これにより、新たに作られるコンテナの名前も「centos7_vim_lsp」になります。(コンテナとイメージの名前が被っていても問題はありません。)

2番目のオプションである「-u」では、UIDを指定しています。ここでは、UIDに110を指定しています。このUIDは、Dockerfileで「tony」というユーザに割り当てたUIDです。また、3番目のオプションである「-it」は、コマンドプロンプトやターミナルからコンテナをインタラクティブに操作するときのおまじないです。

文末から2番目に指定されている「centos7_vim_lsp」は引数で、イメージ名を「docker run」コマンドに渡します。これにより、「centos7_vim_lsp」という名前のイメージから新たにコンテナが作られることになります。(繰り返しになりますが、コンテナとイメージの名前が被っていても問題はありません。)

最後に指定されている「/bin/bash」も引数で、ユーザがログインしたときに起動されるシェルのパスを「docker run」コマンドに渡します。

「docker run」コマンドについては、公式サイトに詳細が載っています。

https://docs.docker.jp/engine/reference/commandline/run.html

「docker run」コマンドでコンテナの作成と起動を行うのは1回だけで良いので、2回目からは起動中のコンテナを操作する「docker exec」コマンドを使います。コマンドプロンプトを開いて次のコマンドを打てばCentOS7にログインできます。

```terminal
docker exec --user tony -it centos7_vim_lsp /bin/bash
```

オプションの意味は、「docker run」コマンドと同じです。

「--user」はユーザ名またはUIDを指定するオプションです。ここでは、ユーザ名として「tony」を指定しています。略記で「-u tony」とも書けますし、さらにUIDで指定して「-u 110」とも書けます。「-it」はコマンドプロンプトやターミナルからコンテナをインタラクティブに操作するときのおまじないです。

引数の意味は、「docker run」コマンドと違います。

文末から2番目に指定されている引数の「centos7_vim_lsp」では、コンテナ名を「docker exec」コマンドに渡しています。これにより、「centos7_vim_lsp」という名前のコンテナが起動されることになります。最後に指定されている引数の「/bin/bash」では、ユーザがログインしたときに起動されるシェルのパスを「docker exec」コマンドに渡しています。

「docker exec」コマンドについては、公式サイトに詳細が載っています。

https://docs.docker.jp/engine/reference/commandline/exec.html

以上がDockerfileからイメージを作り、イメージからコンテナを作って起動するまでの日常的な流れになります。コンテナとイメージの違いは、例えば次のサイトを参考にしてください。イメージからインスタンスをつくるとコンテナになるという理解で大丈夫です。

https://hacknote.jp/archives/56650/

## vimにプラグインをインストール

さぁ、CentOS7にログインできたでしょうか。

うっかり「docker run」コマンドを打ったウィンドウを閉じてしまった人は、再びコマンドプロンプトを開いて次のコマンドを打てばCentOS7にログインできます。

```terminal
docker exec --user tony -it centos7_vim_lsp /bin/bash
```

ログインできた人には、次のような画面が表示されます。
![centos7 login screen.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/9023dced-35f0-2fdc-9557-80a586be55ba.png)
「docker run」コマンドでログインできていた人も、1行目が「docker run」で始まる行であることを除けば、同じ画面が表示されているはずです。2行目のはじめに[tony@342c1576b313 ~]が表示されていますが、これはシェルを操作しているユーザのユーザ名が「tony」、マシン名が「342c1576b313」、ワーキングディレクトリが「~」であることを表しています。「~」はホームディレクトリを意味します。

さて、CentOS7でコマンドを叩いてみましょう。

まず、Dockerfileの「ARG USERNAME=tony」に関係する行を変えていなければ、「whoami」コマンドで「tony」が表示されると思います。これは、いまターミナルを操作しているユーザが「tony」であるということを表しています。

次に、「vim」コマンドでエディタを起動してみましょう。最新のVim8が起動するはずです。
![vim screen of start.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/8cee7f6a-b307-cbc7-48da-b61726f9aa33.png)
vimが起動した状態で、「:PlugInstall」と入力してエンターキー([Enter]キー)を押すとvim-lspと自動補完機能のインストールが始まります。
![vim screen of PlugInstall.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/7f585051-19ae-7a1d-5ae8-9917d255a409.png)
この画像のように「Finishing...Done!」が表示されたら「:quit」と打ってから[Enter]キーを押します。プラグインのインストール画面を抜けて通常のvi/vimの画面に戻ったはずです。

さらに、念のためエスケープキー([ESC]キー)を押してvi/vimのコマンドモードになっていることを確認してから、「:q」と打ってから[Enter]キーを押してvimをいったん終了します。

以上のように、プラグインの導入が手早く終わった理由は「vim-plug」というvimの素敵なプラグインマネージャを使っているからです。興味がある人は例えば次のサイトを読むと、ここで何をしたかが理解できるはずです。

https://qiita.com/kouichi_c/items/e19ccf94b8e5ab6ed18e

## C/C++の自動補完機能のセットアップ

vim-lsp-settingsが凄いのは、PythonだろうがC++だろうが「特定のプログラミング言語のファイルを開いたら拡張を提案される」ところです。

試しにターミナルから「vim test.c」と打って、C言語でコーディングを開始してみましょう。

すると、次のようにvim-lsp-settingsが拡張を提案してくれます。
![vim screen to suggest installing LspInstallServer.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/73cc69f7-d886-83b4-3aaa-ca5fd3104867.png)
一番下に「Please do :LspInstallServer to enable Language Server clangd」と表示されていますが、これが拡張の提案です。「ランゲージサーバclangdを有効にするには:LspInstallServerと打って下さい」という意味になります。ランゲージサーバは、エディタ(vi/vim)にコード補完とか定義ジャンプとかを提供してくれるプログラムで、「clangd」というのが特にC/C++向けのプログラムです。

この提案に従って「:LspInstallServer」と打って[Enter]キーを押すと、本当にインストールするか質問されます。そこで、質問に対して「Y」ボタンを押して[Enter]キーを押すと、次のように「Language Server clangd」のインストールが始まります。
![vim screen of installing LspInstallServer.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/69ccbcb6-f37d-525c-db45-0e32ca8a0913.png)
clangdのインストールが終わると、次のように画面の一番下に「Installed clangd」と出ますので「:quit」と打ってから[Enter]キーを押します。プラグインのインストール画面を抜けて通常のvi/vimの画面に戻ったはずです。
![vim screen of installed LspInstallServer.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/ad2187da-78e0-2771-e040-0bead16d52b0.png)

さらに、念のため[ESC]キーを押してvi/vimのコマンドモードになっていることを確認してから、「:q」と打って[Enter]キーを押してからvimを終了し、ターミナルに戻ります。

:::note info
「:LspInstallServer」と同じように「:LspUninstallServer server-name」と打つと拡張機能をアンインストールすることができます。例えば、インストールした「clangd」をアンインストールするには「:LspUninstallServer clangd」と打ちます。本記事を先に読み進めるには「clangd」をインストールしておく必要がありますので、ここでアンインストールしてしまった人は、上の「C/C++の自動補完機能のセットアップ」からやり直してください。
:::

## 自動補完機能を使ったC/C++のコーディング

では、ターミナルから再び「vim test.c」と打ってvimを起動しましょう。

次のように、「#in」まで打つと自動補完により「#include」の例がいくつか出てきます。[Ctrl]+[n]\(コントロールキーとnボタンを同時に押す\)か[Ctrl]+[p]で候補を選ぶことができます。決定は[Enter]キーではなく、コードの続きを打つと自然に候補が確定されます。
![vim screen of auto completing 1.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/5bf46a23-dddb-07cc-0c4b-fe9e19e5e6c5.png)
よって、候補選択で「#include」が表示されているときにスペースキーを押すと半角スペースが入力されるとともに候補が確定され、次に「\<st」まで打つと「\<stdio.h\>」が候補として選べるようなります。
![vim screen of auto completing 2.PNG](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/128467/3151e9d8-5ea1-1563-6b72-1188e6d939e4.png)
以下、繰り返しで次のようにコードを書き終わったら、[ESC]キーを押してvi/vimのコマンドモードになっていることを確認し、「:q」と打って[Enter]キーを押してvimを終了します。

```c:test.c
#include <stdio.h>

void main(void){
    printf("Hello, world!\n");
}
```
これは本題とは関係ありませんが、次のようにターミナルでコンパイルして実行すれば「Hello, world!」が表示されます。

```terminal
[tony@342c1576b313 ~]$ gcc test.c -o test
[tony@342c1576b313 ~]$ ./test
Hello, world!
[tony@342c1576b313 ~]$ 
```

補完機能の操作方法が分かりにくい場合は、例えば次のサイトが参考になります。

https://qiita.com/kouichi_c/items/5f047ab3a7c64277e97c

また、vim-lsp-settingについては、次の公式サイトに詳しい情報があります。

https://github.com/mattn/vim-lsp-settings

英語が読めないという人は、無料でも使える翻訳サービスの「[DeepL翻訳](https://www.deepl.com/ja/translator)」を使うと良いと思います。

# おわりに

いかがでしたでしょうか。

パッケージマネージャである「vim-plug」とランゲージサーバの簡易導入ツールである「vim-lsp-settings」が入ったことにより、vimが格段に使いやすくなりました。

他にもいろいろな機能がありますので、私も引き続きいろいろと試してみようと思います。Dockerだと環境が汚れないので気が楽ですよね。

それでは、皆さまが今日も楽しくプログラミングできますように！