LoginSignup
17
8

More than 5 years have passed since last update.

Qiita-vim復活させた話 [完成!!!]

Last updated at Posted at 2018-10-13

PR通ってます!!!(跳ねるほど嬉しい)

随分と前のことになりますが、
PR対応をしていただいたため、Qiitaにvimから投稿できるようになりました!!!
やったね!!

[new] Todo list公開しときました、issueでできればいいのだがどうやらforkにはissueかけないようなので...
https://qiita.com/Cj-bc/items/c0bcb8e3e7de86c821b9

進捗報告(最新版)

なんか割と需要ありそうなので書いておきます。
現状下に貼っているgithubから入手できますが、細かい部分をケアしていないため不具合多数出る可能性もあります...

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> O
:Qiita(投稿) O
:Qiita -e(編集) O
:Qiita -d O エラー時のメッセージがおかしいのでちょっと直す必要があります。
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita O
:CtrlPQiita O
API libraryとして O 詳しくは下記

API libraryとして

method 状態 docs 備考
qiita#createApi O O
qiita#createApiWithAuth - - そもそもパスワード認証用のAPIがなくなっているようなので消滅
api.rate_limit O O APIエンドポイントは消えているので、headerから取得する
api.user O O
api.tag O O 新たなエンドポイントが増えているが、描き直すべきか否か
api.tags O O
api.post_item O O
user.items O O
user.item O O
user.stocks O O
tag.item O O
item.delete O O

Qiita-vimとは

@mattn 氏作の、vimからQiitaへの投稿、閲覧、更新など色々できるプラグインです。
しかし最終更新日時が6年前、Qiita API v2に対応していないため現在使うことができません。
しかしどうしてもvimからQiitaを使いたい(Qiita書くためだけにGUI環境に出てきたくない)ので、自分でなんとか対応させてみることにしました。

Qiita: mattn/Qiita.vim 作りました!
Github: mattn/qiita-vim
作業中のfork: Cj-bc/qiita-vim, feature/v2 branch

デバッグ方法

主には、:debugと変数の定数置き換えによってデバッグしました。
:debugについてはちょっと書いたのでこちらを: Cj-bc/vim scriptデバッグ方法

定数書き換えとは

勝手に呼んでいるだけです()
挙動が怪しいなと思ったところを定数に書き換え、同時に複数の不具合が起こらないようにしました。
今回は、固定できるところ、つまりself.url_name(QiitaのURLにある名前。@以下のやつ。)を自分の名前(Cj-bc)に固定したり、
今回のケースでは省いてもそこまで問題がなさそうな処理を省いたりしました。

具体例ですがほんのちょっとだけ長くなるのでしまっておきます

例を置いておきます、コメントアウトされているのがテストのためにいじったコードです。
実際のデバッグ時にはそれらのコメントアウトを外し、既存の該当する部分と置き換えて走らせました。

list_user_items()
    ...
  try
    let old_undolevels = &undolevels
    silent %d _
    redraw | echon 'Listing items... '
    let items = a:api.user(a:user).items()
    "call setline(1, split(join(["this is test", "second line"], "\n"))) " for debug
    "call setline(1, split(join(map(items, 'v:val.uuid . ": " . v:val.title'), "\n"), "\n")) " for debug 2
    call setline(1, split(join(map(items, 'v:val.uuid . ": " . webapi#html#decodeEntityReference(v:val.title)'), "\n"), "\n"))
  catch
    bw!
    ...
user.items()
function! s:user.items()
  "let res = webapi#json#decode(webapi#http#get(printf('https://qiita.com/api/v2/users/%s/items', 'Cj-bc'), {'token': self.token}).content)
  let res = webapi#json#decode(webapi#http#get(printf('https://qiita.com/api/v2/users/%s/items', self.url_name), {'token': self.token}).content)
  if type(res) == 4 && has_key(res, 'error')
    throw res.error
  endif
  if type(res) != 3
    throw 'invalid response'
  endif
  ...

APIをv2に書き換える

これは至極単純で

:%s/v1/v2/g

で行いました。
一応、間違って置換されてないか確かめるために/v2で確認もしました

2. うまく走らないのをなんとかする

大雑把ですがめっちゃ時間かかりました...
ちなみに、大掛かりにデバッグとかしてますが遠回りしただけだったりします(単純に仕様変更のせいだったりした)

2.1 Vim(call):E716: Key not present in Dictionary: uuid . ": " . v:val.title

これです

qiita-vim-err-uuid.gif
)

これは結局、APIの仕様変更に伴い削除された値を読みに行っていたのが問題でした。
具体的にはここ

list_user_items()
call setline(1, split(join(map(items, 'v:val.uuid . ": " . webapi#html#decodeEntityReference(v:val.title)'), "\n"), "\n"))

github
使用されているAPI: /api/v2/users/:user_id/items

前後のコード読まないとなんともわからないとは思いますが、v:val.uuidの部分が違っていました。
これ、uuidは削除されてるんですね...v2だと...
代わりにidなるものがありましたので、置き換えたら動きました。やったね。

解決までの道のり

call setline(1, split(join(["this is test", "second line"], "\n")))

まずこれで置き換えてエラーが出ないことを確認。置き換える前の部分に問題があるなと推定(ちょっと雑すぎるかも?)

call setline(1, split(join(map(items, 'v:val.uuid . ": " . v:val.title)'), "\n"), "\n"))

webapi#html#decodeEntityReferenceの部分は、URLエンコードを外すためのものらしく、今回の件とは関係なさそうなのでとりあえず外す。
この時点で文字列は表示されてなかったため、URLエンコードされたままでもまぁ問題ないと判断。
これでもエラーが出る& エラーが.uuidで始まることから、uuidを疑う。
ここでリファレンス見たらuuidなるものが存在せず、代わりにidなるものがあるので使って見た。
そしたら動いたので置換しておいた。
ここまで約1日かかった...エラーからすぐに見破れなかったのはちょっと悲しい

この後webapi#html#decodeEntityReferenceももとに戻してエラーが出ないことを確認、この部分についてはおそらくこれで完了

現状

:Qiita -lコマンドを実行すると

Qiita -lコマンド実行結果1
こうなる。

ここで項目を選択すると
Qiita -lコマンド実行結果2
開ける!!

2.2 E716: Key not present in Dictionary: url

Qiita-vim 2.2実行結果1

こいつはなかなかトリッキーでした
トリッキーというか、:debugをみるまではわかりづらかった...

これはwrite_item()を実行している間に出たものなのですが、ここで使用されているAPIを調べると、引数にurlパラメータは存在している。おかしい。
結局をいうと、実は認証がうまく行っていなかったのだということがわかった

Qiita-vim 2.2実行結果2

とりあえずtokenを変えてみたりはしたがうまくいかない。この解決策に関しては後述する(未作成なので作成次第リンク貼ります)として、何よりこのエラーを消そうと思う(認証できなかった場合ははっきりそう明記させたい)。
つまり、エラーハンドリングをする。

エラー判別を直す

APIの仕様変更により、API側からのエラーの返り方に変更があったようです。
そのため、きちんとエラーが捕捉されずに放置されてしまったわけです...
なので、リファレンスをみながら該当箇所を直します
具体的には、エラーのレスポンスが以下のように変更されています:

{
  "message": "Not found",
  "type": "not_found"
}

元のコードではerrorが含まれる場合をエラーとみなしていたので、すり抜けていたわけですね。
直します

- if has_key(res, 'error')
+ if has_key(res, 'type')
-  throw res.error
+  throw res.type
  endif

捕捉するのはtypeでいいのか悩んだのですが、他にtypeが返ってくることはなさそうだったのでこれにしました。
問題ないといいな...?

脇道に逸れて例外捕捉

本題とは逸れますがこれも改善の一歩ということで...(というか、きちんとハンドリングしてくれた方がデバッグも楽)

さて、一箇所だけtry-catch使っている部分がありますので

list_user_items()
try
  ...
catch
  bw!
  redraw
  echohl ErrorMsg | echomsg v:exception | echohl None
  return
finally
  ...
endtry

ここを参考にして、例外捕捉を行いました
具体的な設置箇所:

箇所 いつ
qiita#login() qiita#createApiWithAuth実行時
write_item() api.post_item実行時
list_user_items() a:api.user(a:user).items()周り実行時
qiita#Qiita()   qiita#login実行時

この結果、きちんとエラーを表示できるようになりました。

~/.qiita-vim(設定ファイル)が存在するけどtokenが違う/ない
などで認証できない場合:
qiita-vim-err-add-trycatch1.gif

~/.qiita-vimがなくて、プロンプトから入力してるけど認証できない場合:
qiita-vim-err-add-trycatch2.gif

もうちょっとわかりやすくできそうだけど...今はとりあえずこんなもんでいいかなと(エラー情報どのくらい表示するかは悩む)
一つ目の方のエラーメッセージは、「tokenが違います」的なのを出せたら良いかもしれない。

認証できない問題

やっと解消できました...
これもまた、APIの仕様変更によるものでした。

APIバージョン token送信方法
v1 Jsonでbodyの中身として送信
v2 HeaderにAuthorization: Bearer <token>として埋め込み

これを直して一件落着...かと思いきやまだダメだった!
とりあえず認証は通るようになり、代わりに2.3のエラーが出るようになりました

現状

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> X 何故か自分のものが表示されてしまう
:Qiita(投稿) 作業中
:Qiita -e(編集) X
:Qiita -d X
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita X
:CtrlPQiita X
qiita-interfaceとして X まだきちんと調べてない、コマンド系終わったら対応

2.3 bad_request/invalid_json

ま〜、ヒントの少ない問題だこと...
APIの認証は無事に通るようになりましたが、今度はJSON書式がなっていないのだそうです。
与えられた情報はこれだけ

{
  "message": "invalid_json",
  "type": "invalid_json"
}
{
  "message": "bad_request",
  "type": "bad_request"
}

まー情報の少ないこと...でも逆に楽しくなってきてガシガシやってました。

先に結論

bodyprivateキーに与える値がbool値でなかったことが問題だったようです。
これは、vimの仕様変更に伴うものなのか、それともQiita側の仕様変更に伴うものなのか...わかりませんがとりあえず直しました。

-   \ "private": 0,
+   \ "private": v:false,

そして、vim8から搭載された、json_encodejson_decodewebapi#json#encode/webapi#json#decodeの代わりに採用しました。
(そうでないとwebapi#json#encode/webapi#json#decodeのアップデートに時間がかかってしまうため。)

解決までの道のり

まず、怪しいのはbody部分ぽかったので、何か必要な値が足りなかったり、不要なものが入ってたりしないか?とリファレンス見ながら探してました。
が、特になし。(tweetgistを追加してみたりしたが変化なかった)

上記作業中に、privateの値について疑問を持ちました。
リファレンスによればbool値なのに何故0で指定されていたからです。
まぁおそらく、01(もしくは0とそれ以外)などで真偽を書いているのだろうと推測したものの気になって調べました。
すると、vim8になってから真偽値が追加されたとの情報が!!

引用:

Vim script には、true/false などの bool 値や、null などの値は存在しませんでした。
このままだと JSON と相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。

v:false
v:true
v:null
v:none
これらによって、bool 値や null を含む JSON も正しく相互変換されます。

Vim 8.0 Advent Calendar 4 日目 JSON サポート

これにならい、v:false/v:trueに書き換えて動かすと、
うご...きそうなんだけど動かない...
今度はinvalid_jsonって言われる。
どうやらJSONの書式か何かがおかしいようです。

...お?そういえばさっき何か見たな??

これだ

引用:
Vim script には、true/false などの bool 値や、null などの値は存在しませんでした。
このままだと JSON と相互に変換するのに支障が出るため、新しくこれらを表す値が追加されました。

Qiita-vimでは、JSONのエンコード・デコードにwebapi-vimの関数が使われていました。
しかしそれらはvim8以前の更新が最後となっているため、vim8で追加されたv:false/v:trueなどに対応していませんでした。そのためうまくエンコードしてくれなかったわけです。
webapiに手直しをするか...?本来はそれもしたいけど、あまり割いている時間はなかったので、先ほどのページで見かけたjson_encode/json_decodeを使用することにしました。
すると...

綺麗にできた!!!

現状

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> X 何故か自分のものが表示されてしまう
:Qiita(投稿) O
:Qiita -e(編集) X
:Qiita -d X
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita X
:CtrlPQiita X
qiita-interfaceとして X まだきちんと調べてない、コマンド系終わったら対応

2.4 :Qiita -l <username>で自分の投稿一覧が表示されてしまう

謎だったものの一つ...
何故か、:Qiita -l <username>としても:Qiita -lと同じ動作になってしまっていました。
わかってしまったら簡単だったんですがね...

解決までの道のり

とりあえず、qiita#Qiitaからコードを辿り、その後:debug Qiita -l <username>でステップ実行しながら確認しました。
初めは、qiita#Qiitaから<username>がうまく伝えられてないのかな?と思ったのですが問題ありませんでした。
その後もlist_user_items内でも問題はなく...
若干書くのがめんどくなってきた(単純作業でしかなかったので)ので結論を書きます。
....まさかの自分のミス....
前、問題解決する為に付け足したコードが悪さをしたようです...
具体的には、これまではurl_nameというパラメータが存在していたのにv2にてなくなっていたことが原因でした。
url_nameはつまり、@cj-bcなどの名前に当たるわけで、それをtokenの持ち主のものに設定するように書いてしまっていた...(元はそもそも存在していなかった)
なので、これをきちんと正しいユーザーの情報になるように変更を入れました。

そしたら...きちんと動くようになった!!!
スクリーンショット 2018-10-14 22.46.39.png

(テスト用にmattnさんの名前で調べてみました。)

2.5 :Qiita -eが動かない

:Qiita -eを実行すると

write_item: updating item: Vim(let):E474: Invalid argument

こんな感じのこと言われます。(ログのプレフィックスは適当)

先に結論

編集するためのAPIに渡すパラメータが不要に多かったのと、HTTP methodが違っていたのが問題でした。
そのため、リファレンスに合わせてパラメータを減らし、HTTP methodの修正をしました。

+ let content = {'body': self['body'],
+              \ 'id': self['id'],
+              \ 'title': self['title'],
+              \ 'tags': self['tags'],
+              \ 'private': v:false,
- let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(self), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PUT', 'Authorization': 'Bearer ' . self.token}).content)
+ let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(content), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PATCH', 'Authorization': 'Bearer ' . self.token}).content)

解決までの道のり

invalid argumentと言われているのでとりあえずリファレンスを参照します。
するとHTTP methodがPATCHである事がわかったので変更。
HTTP methodが規定されているのはここ

item.update()
  let res = json_decode(webapi#http#post(printf('https://qiita.com/api/v2/items/%s', self['id']), json_encode(self), {'Content-Type': 'application/json', 'X-HTTP-Method-Override': 'PUT', 'Authorization': 'Bearer ' . self.token}).content)

'X-HTTP-Method-Override': 'PUT'として指定されているので、これをPATCHに変更します。
webapi#http#postの仕様上、第四引数にHTTP methodを指定することもできますが、元のスタイルに合わせておきました。
しかしこれでも動かない...
invalid argumentなので:debug Qiita -eでトレースして見ると、どうやらjson_encodeでエラーを吐かれているらしい。
json_encode(self)となっているが、selfを覗いてみると、リファレンスをみる限り不要な情報も同時に投げているようでした。
そこに意図があったのか、(それともただ単に綺麗なコードだからか)わかりませんが、渡す内容を削ってみることにしました。

  let content = {'body': self['body'],
               \ 'id': self['id'],
               \ 'title': self['title'],
               \ 'tags': self['tags'],
               \ 'private': v:false,
               \}

このように定義し、json_encode(self)の代わりにjson_encode(content)として動かすと...

[後でここに画像が多分きます。wifiないのでおうち帰ってから。]

うまく動いた!!
元々は、APIに渡す情報が過多だったのかなと思い、必要最低限と思われるところまで削ったわけですが、
おそらく情報過多なわけではなかったと思います(そもそもAPIを叩きに行っていなかったため)
結果オーライなのですが、きちんと検証した方が良いかも...?
ということで...

現状

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> O
:Qiita(投稿) O
:Qiita -e(編集) O
:Qiita -d X
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita X
:CtrlPQiita X
qiita-interfaceとして X まだきちんと調べてない、コマンド系終わったら対応

2.6 :Qiita -dの修正

これは割と簡単だった。ってかほんと単純に直ったのであまり書くことありません。
徐々に記事更新がだるくなってきており(主にはリアルタイムで書いていないのとSS撮り忘れていて資料がなかったりするため)、
若干雑になってきてるけどゴメンなさい(雑でも最後まで書きあげる方を優先したい)

まず、HTTP methodの指定方法がよくなかったので直します。

webapi#http#postへの引数内で、method指定する引数が{X-HTTP-Method-Override: 'DELETE}となっていた部分を修正しました。
二通り方法がある(methodへの引数としてDELETEのみを指定するor先ほどの辞書をheaderのリストに追加する)のですが、
他の箇所との一貫性を考え後者にしました。

エラー捕捉の修正

前は、リクエストの戻り値のheaderにHTTP status codeがあったようですが、現状だとstatusとして分離されていたので、それに合わせて書き直しました。
とりあえずそんなところです。

現状

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> O
:Qiita(投稿) O
:Qiita -e(編集) O
:Qiita -d O エラー時のメッセージがおかしいのでちょっと直す必要があります。
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita X
:CtrlPQiita X
qiita-interfaceとして X まだきちんと調べてない、コマンド系終わったら対応

2.7 Unite/CtrlP対応

これは単純にuuididに変更しただけで動きました。
ただ、自分は普段どちらも使っていないため、見つかってない不具合がまだある可能性があります...
PRのコードレビューの時に見つかってほしい...(投げやりorz)

現状

コマンド 状態 備考
:Qiita -l O
:Qiita -lのリストから項目選択 O
:Qiita -l <user> O
:Qiita(投稿) O
:Qiita -e(編集) O
:Qiita -d O エラー時のメッセージがおかしいのでちょっと直す必要があります。
:Qiita XXXXXXXX O 指定されたitem返すやつ
:Unite qiita O
:CtrlPQiita O
qiita-interfaceとして X まだきちんと調べてない、コマンド系終わったら対応

この先も随時更新しまふ

17
8
13

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
17
8