ついにJupyterLabの入力補完？？Tab押さずに補完してくれるjupyterlab-lspを試してみた

Jupyter notebookからさらに色々便利になっているJupyterLab。
便利な一方で、notebook側の入力補完の拡張機能の[hinterland](https://github.com/ipython-contrib/jupyter_contrib_nbextensions/tree/master/src/jupyter_contrib_nbextensions/nbextensions/hinterland)などが対応していません。

Tabを押せば補完は表示してくれる（且つ、補完がnotebookよりも賢い）ものの、できればVS Codeなどと同じ感覚でTabを押さなくても補完して欲しいところです。

Labのβが出てから長いこと「誰か作ってくれないかなー」と他力本願なことを考えていましたが、ついにそれらしきライブラリが公開されたようです。

[Language Server Protocol integration for JupyterLab](https://github.com/krassowski/jupyterlab-lsp)

※まだ現時点だと、公開されたばかりですし実験的な側面が強いようです。

> This extension is highly experimental, though you are encouraged to try it, leave feedback and/or a PR

どんな感じなのか大分気になるので、早速触ってみます。

# jupyterlab-lspのここが凄い！（機能紹介）

インストールなどよりも先にどんなライブラリなのか書いておきます。

## `.`や()を書いた際に入力候補や引数情報を表示してくれる

Tabを押さなくても表示してくれます。
また、JupyterLabからはVS CodeやSublimeやAtomのように、完全一致でなくても候補の絞り込みができます（これは拡張機能関係なく、JupyterLabはデフォルトでTabキーでもそういった挙動になります）。

![20190823_5.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/eb8c5130-b5bf-a6b7-46ac-d4cbfb5f94d2.gif)


ただ、現在最初の変数などをタイピングした際には補完候補が出てこない（例 : 前述のアニメーションで`cat`と入力した際など）ようで、その辺りはTabを押す必要があります（将来アップデートで対応いただけるかもしれません）。
READMEの`Coming soon:`の節を見てみると、そのうちご対応いただけそうな気配はあります・・。

> to enable aggressive autocompletion (like in hinterland)

## カーネルが死んでいても補完が効く

基本的に今までJupyterで使われていたHinterlandやTabでの補完では、カーネル上で一度実行しないと補完に出てきてくれません（対象の変数などのセルを実行しないと、補完のリストに出てこない）。
一方で、jupyterlab-lspでは、カーネル起動直後からノート内の記述に対して補完が効きます。さらに、カーネルが死んでいても補完が効きます（まあでもdead kernelのまま作業することはほぼ無いとは思いますが・・・）。

![20190823_3.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/632e4dbf-e6c1-4e82-17fe-697be79d373d.gif)

## 関数内のローカル変数などでも補完が効く

さらに言えば、今までHinterlandやTabでの補完ではグローバル変数など以外効かなかった、関数内でのローカル変数に対しても補完が効くようになります・・・！（JupyterがかなりVS Codeなどのプログラミングエディタに近づいた感が・・凄い）

![20190823_4.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/62276ce3-227f-aab9-fa76-5ff344c2c0e9.gif)

## 関数の引数でも、型をアノテーションすれば補完してくれる

Python3.5から追加された、関数の引数などへの型のアノテーションの機能を使うと、こちらも補完してくれます。
ローカル変数の件も含め、Jupyter上だと関数などを書くのが補完が効かずやりづらいところが改善しています。

> autocompleter with documentation and sorting based on LSP suggestions

![20190823_6.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/17917113-761e-a77e-e26a-bdf94600a297.gif)

また、返却値に関しても型のアノテーションをしてある場合、関数実行前（同じセル内で実行する際など）から返却値に対して補完が効きます。

![20190823_7.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/7e6f91c1-306d-2750-a15f-64306311fbca.gif)

ループなどのイテレーションで設定される変数に関しても、型アノテーションで補完が効きます。

![20190823_8.gif](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/e9f201ed-d6ca-95d0-569e-bd9e408f1d1b.gif)

## Lintなどが自動で実行される

こちらもVS Codeなどでお馴染みな機能かもしれません。
今まではJupyterで拡張機能などでUIを操作しないとLintなどが走らない・・・といったケースが多かった（探せばあるかもしれませんが・・）ですが、この拡張機能だとコーディング中にリアルタイムにpyflakesなどのLintでチェックしてくれます。

![20190823_1.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/cf9ca197-9d9c-bca2-b9e3-fcbf2b52ae28.png)

PEP8（pycodestyle）的なところもチェックしてくれます。

![20190823_3.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/ec9295cd-e89d-61d9-e6c0-a52c33db6acf.png)


# 留意事項

現在βバージョン的な状態で、直近でも色々コードが更新されていますので、以下のものは古い箇所がそのうち出てくると思われます。予めご了承ください（2019年8月ごろの時点の内容です）。

例えば、githubのリポジトリのREADMEに書いてある、

> auto invocation of completer on certain characters, for example '.' (dot) in Python

とか、

> automatic signature suggestions

とか、この記事を書き始めた日には実装されていなかったものの、その翌日には既に実装されているというスピード感です（その翌日にはさらにissueで提案させていただいた内容のアップデートがリリースされるという・・・）。
恐らく、本記事もすぐに古くなってしまうかと思われます。

# インストール

とりあえずJupyterLabはインストールして、起動できるようにしておきます（色々な方が既に書かれているのでこの辺りは割愛します）。

本記事では以下の環境を使っています。

- Windows
- 1.0.2のバージョンのJupyterLab。

## node.jsのインストール

まずは、node.jsが必要なようなのでそちらのインストールをします。入れてないと以下のように怒られます。

```
Please install nodejs >=6.11.5 before continuing. nodejs may be installed using conda or directly from the nodejs website.
```

また、node.jsの公式ページ（ https://nodejs.org/en/ ）からインストールしたものだと、以下のエラーになってしまいました。

```
"@krassowski/jupyterlab-lsp" is not a valid npm package
```

そのため、conda経由で以下のコマンドでインストールします。

```console
$ conda install -c conda-forge nodejs
```

また、1.0.xからはExtensionManagerという機能が用意されています（まだ、実験的な実装なそうです）。
そちらから検索して拡張機能をインストールしたりできるのですが、今回はコマンドラインで進めます（そのままだとエラーになってしまうのですが、後述の文字コード関係の対応をしてからJupyterLabを起動すれば、ExtensionManagerでもいける気がします）。

```console
$ jupyter labextension install "@krassowski/jupyterlab-lsp" --debug
```

日本語環境での都合（コンソールでのcp932などの出力の都合なのか）以下のようなエラーがAnaconda Promptなどのコマンドラインで出てしまいます。

```
UnicodeDecodeError: 'cp932' codec can't decode byte 0xef in position 7095: illegal multibyte sequence
```

```
IndexError: list index out of range
```

そのため、一度コマンドラインの設定で、cp932（sjisの親戚）ではなく、`437 （OEM - 米国）`の方を選択して、コマンドラインを再起動します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/707f8b01-5dda-1a86-da36-d14aec9787fd.png)

恐らく、日本語環境だとAnaconda Promptなどでcp932の方が選択されていることが多いと思いますので、要注意です。

※ビルトインモジュールでのエラーで、少々JupyterLab関係で調べたのですがどうにも解決策が見つかりませんでした。
cp932周りなので、Ubuntuとかなら普通にいけるのかな・・という感じではあります（[#7](https://github.com/krassowski/jupyterlab-lsp/issues/7)でやりとりしていても、Pythonのコードがない拡張機能とのことで、JupyterLab側とかのアップデートが必要かもしれない・・という印象です。）。

続いて必要な`python-language-server`ライブラリをインストールします。

```console
$ pip install python-language-server[all]
```

```
Successfully installed autopep8-1.4.4 pydocstyle-4.0.1 python-jsonrpc-server-0.2.0 python-language-server-0.28.1 typed-ast-1.4.0 yapf-0.28.0
```

servers.ymlという名前のファイルを任意の場所に用意し、以下の内容を記載します。私はAnaconda関係のワークスペースに設置しました。

```yaml
langservers:
  python:
    - pyls
```

最後に、JupyterLab起動前に以下のコマンドでnodeのプロセスを起動しておきます。

```console
$ node C:/Users/****/Anaconda3/share/jupyter/lab/staging/node_modules/jsonrpc-ws-proxy/dist/server.js --port 3000 --languageServers servers.yml
```

※stagingまでのパスは環境によって様々だと思いますので調整ください。今回はWindowsを使っていますが、Ubuntuだと`~/.local/lib/python3.6/site-packages/jupyterlab/staging/`といったようなパスになったりするそうです。他にも、pyenvなどを使った場合も変わってきます。
servers.ymlのパスは設置した場所を指定してください。

なお、私の環境ではnode.js関係で若干エラーが出ました。ただし、動作は特に問題なさそうなので一旦無視します（そのうちアップデートで消えるかな・・・という印象です）。

node.jsのプロセスが起動し終わったら、今度はJupyterLabの方を起動します。この辺りの説明は割愛します。

```console
$ jupyter lab
```

起動後のJupyterLabで、Pythonのノートを開いてしばらくすると機能が有効化されます（最初は少し時間がかかりました）。

もし有効化されていない場合、一度ノートをF5とかしてみてnode.jsのコンソールに`Forwarding new client`といった表示が出ていることを確認してみてください。出ていない場合、node.js側が止まっている？感があり、一度コンソール上でEnterを押したら直りました（もしくは、最初は少し不安定な感じもあるため、一度F5するなど）。

# 余談と感想

- まだちょっと正式版という感じでもないので、インストールや一部機能は不安定な感じがあり（特に日本語環境特有のものなど）、皆さんの環境でもインストールがうまく進まないとか出てくると思われます。そういった場合はissueで報告したりプルリクとか出してみたりしてもいいかもしれません。
- とはいえ待望のHinterland代替となりうる拡張機能で、この拡張機能のおかげでJupyterLabにそろそろ移行するか・・・と思えるくらいに感動した、今後期待大のライブラリだと思いました・・！（小並感）
- プルリク出そうにも、TypeScriptが分からないとなので辛いところです。そろそろTypeScript学んでおくべきなのでしょうか・・・（結構不便に感じることが多い・・・）


---

※追記 : 型のアノテーションでサードパーティーのライブラリの補完が効かなそう、と記載していましたが、何が影響したのか分かりませんが、ipythonのバージョンを7.2.0に下げたり、[#12](https://github.com/krassowski/jupyterlab-lsp/issues/12)でご紹介いただいた`pyls-mypy`をインストールしていたりしていたら、普通にサードパーティーのライブラリも補完が効きました・・・（ipythonの影響なのか、pyls-mypyのおかげなのか、いつの間にか直っていたので原因が分からずすっきりしませんが・・）。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/228778/b4b9b0d2-7417-c22f-dfd5-6e4aec614356.png)

ただ、データフレームは元のコードが大きいからなのか、型のアノテーションでは補完が表示されるまで結構時間がかかったりするようです。NumPy配列などはさくっと補完されました。

恐らくmypy（[Mypy plugin for PYLS](https://github.com/tomv564/pyls-mypy)）でのtypeshed周りが影響していそうな・・気がします。もしサードパーティーライブラリの補完がうまいこと効かない場合は、インストールの際に以下の追加のpipコマンドや、node.jsやJypyterLabの再起動を試してみてください。

```
$ pip install pyls-mypy
```

---

# 参考文献

- [Language Server Protocol integration for JupyterLab](https://github.com/krassowski/jupyterlab-lsp)
- [ValueError: "@jupyter-widgets/jupyterlab-manager" is not a valid npm package](https://github.com/jupyter-widgets/ipywidgets/issues/1982)
- [jedi raise AttributeError('There are no params defined on this](https://github.com/jupyter/notebook/issues/4364)
- [Mypy plugin for PYLS](https://github.com/tomv564/pyls-mypy)