Panda Noir

JavaScript の限界を究めるブログでした。最近はいろんな分野を幅広めに書いてます。

Defx.nvim でプレビューウィンドウを自動的に閉じる

Defx.nvim には floating window を使ったイカしたプレビュー機能があります。しかし、何も設定をしていないと、defx を閉じたあともプレビューウィンドウは残ります。この記事は autocmd を活用してプレビューをうまく消そうという記事です。

Defx を閉じたタイミングを取得する

Defx.nvim のウィンドウを閉じると同時にプレビューウィンドウを閉じるには、ウィンドウが閉じたことを検知する必要があります。ウィンドウが閉じたときに発火するイベントはいくつかあります。

  • BufLeave
  • BufHidden
  • BufEnter(ほかのウィンドウに入った=前のウィンドウを離れたとみなせる)

Defx は独自に DefxClosed のようなイベントを発火しないので、これらを使わなければなりません。今回はBufHidden を使います。

BufHidden が発火したときに pclose (プレビューウィンドウを閉じるコマンド)を実行すればやりたいことが実現できます。

(BufLeave ではプレビューウィンドウを開いたタイミングにも発火するのでうまくいきません。また、BufEnter は defx のウィンドウが閉じられたのかを検知できません)

autocmd BufHidden \[defx\]* pclose

これで defx#do_action('quit') などで閉じればプレビューウィンドウも消えます。

ついでに: Gitのプロジェクトルートで defx を開く

あまりに記事のボリュームが少ないので、ついでに全く関係ない Tips をいくつか盛っておきます。

Defx はデフォルトでは現在のディレクトリを表示します。しかし、プロジェクトルート(.gitなどのある位置)で開いたほうが便利ですよね?というわけで、 DefxProjectFile() という関数を作りました。といってもほとんど fzf のリポジトリにある ProjectFiles を参考に書いただけですが。

nnoremap <silent> ,f :Defx `system('git rev-parse --show-toplevel 2> /dev/null')[:-2]` -search=`expand('%:p')`<CR>

ウィンドウが defx のみになったら vim を終了

quit をしてもファイラーウィンドウが残ってしまうのが嬉しいことはほとんどないので、ファイラーウィンドウのみになったら quit を実行するようにしてみました。

function! s:previewWindowOpened() abort
    for nr in range(1, winnr('$'))
        if getwinvar(nr, "&pvw") == 1
            return 1
        endif
    endfor
    return 0
endfunction

autocmd WinEnter \[defx\]* if winnr('$') == 1 || winnr('$') == 2 && <SID>previewWindowOpened() | quit | endif

WinEnter が発火したとき、 defx のウィンドウのみ、あるいは defx ウィンドウとプレビューウィンドウのみだったら終了します。

previewWindowOpened関数はこちらを参考にして、一部動かなかった箇所を修正しています。

.eslintrcを別フォルダに移す

ルートディレクトリにコンフィグファイルがちらばっていると気になりませんか?僕はとても気になります。そこで、設定ファイルを別ディレクトリへ移す方法をご紹介します。

package.jsonに追記するだけで良い

たとえば.config/eslintrc.jsへ移動させたとします。そしたら、以下をpackage.jsonへ記述します。

{
  ...
  "eslintConfig": {
    "extends": "./.config/eslintrc.js"
  },
  ...
}

基本的にはこれだけでOKです。

原理

extendsをつかってファイルを指定すると、そのファイルに書かれた設定が読み込まれます。今回でいえば.config/eslintrc.jsを使うように設定してあるので、これまでと同様に扱うことができます。

上の設定はエディタのプラグインともうまく協調します。また、すでに書いたnpm-scriptsのeslint部分をいじる必要もありません。

Vue3に書き直してみる

Vue2からVue3へ書き直す際に情報が不足していて困ったのでまとめておきます。

公式ドキュメント

まだversion3の公式ドキュメントは揃っていません。マージされたPRRFCを見るしかない状況です。

migrate from v2 to v3

※「これだけ押さえれば7割くらいは移行できるだろう」くらいで網羅的には書けておりません

  1. new Vueによるインスタンス生成はcreateApp()へと置き換え
  2. setupメソッドの引数の型推論をしたい場合はSFCのexportするオブジェクトをdefineComponentでラップ
  3. new Vueのdataやmethodsはinject()app.provide()を使ってグローバルステートで置き換え React Context APIのようなもので、書き方もだいたい同じ。
  4. watchやdata、computed、mountedなどほとんどのプロパティは消えてsetupへ集約
  5. vue-template-compilerの代わりに@vue/compiler-sfcを使う
  6. shims-vue.d.ts(SFCの型定義ファイル)の書き方が変わる
  7. render関数の引数として渡されていたh関数はグローバルインポート

インストール

バージョンは2020年6月19日現在のものです。

  • vue@3.0.0-beta.15

SFCで書きたい、webpackを使いたいのであれば以下も必要です。

  • vue-loader@16.0.0
  • @vue/compiler-sfc@3.0.0-beta.15
  • webpack, webpack-cli

書き方

大きく異なる点がいくつかあります

  • composition APIが使える
  • webpackのalias設定がいらなくなる
  • new Vueが消える
  • Vueオブジェクトに生えていたAPIがnamed exportsオンリーになる
  • TypeScriptとの親和性が高くなる

composition APIが使える

まずこれがかなり大きいです。このお陰でVueへもTypeScriptを導入しやすくなりました。this関連でひどい目に合わずに済むのは大変嬉しいです。

また、書き方もかなり楽になりました。ロジックを外へ抽出できるようになった点も嬉しいです。ReactのHooksとできることはほぼ同じですが、かなりVueらしいやり方に収まった感覚があり、とても好きです。

webpackのalias設定がいらなくなる

new Vueの代わりにcreateAppというメソッドを使ってマウントするようになりました。そのため、vue/dist/vue.esm.jsへエイリアスを張らなくて良くなります。逆に、Vue2の頃のaliasがwebpack.config.jsに残っているとビルドが失敗します。

createApp

こいつが結構やっかいです。従来のやりかたと所々異なっています。

rfcs/0009-global-api-change.md at 9f18645a700f54e7d9a4e3b53046d48791e31459 · vuejs/rfcs · GitHub

上記リンク先を見ていただけばわかりますが、ほとんどは同じです。しかし、たとえば下のようなケースでは若干変わってきます。

// v2
new Vue({
  render: (h) =>
    h(MyComponent, {
      props: { prop1, prop2 },
    })
})
// v3
const app = createApp(MyComponent, { prop1, prop2 });

僕はかなり泥沼にハマりました。

defineComponentについて

setupのpropで型推論を働かせたいときにはdefineComponentでラップする必要があります。逆に、setupでpropを参照しないのならdefineComponentで囲む必要はなく、従来のSFCの書き方で十分です。

公式にも書いてありますが、defineComponent自体は受け取ったオブジェクトをただ返すだけの関数で、型推論以外では1ミリも役に立ちません。

Note that implementation-wise defineComponent does nothing - it simply returns the object passed to it. However, in terms of typing, the returned value has a synthetic type of a constructor for manual render function, TSX and IDE tooling support. This mismatch is an intentional trade-off.

JSXについて

renderのなかでJSXを使えるようになりました(今まではh関数を使ったcreateElement的な書き方しかできなかった)。これでvueファイルの中の<template>を消してJSオンリーでかけるようになりました。

Global APIのnamed exports化

tree-shakingのための変更です。Vue以下に生えていたVue.nextTickなどはnextTick関数としてexportされるようになります。Vue.nextTickは使えなくなります。このように、Vue3には後方互換性のない変更があるので注意が必要です。

SFCの型定義が変わる

new Vueによるインスタンス生成から変わった影響で、SFCの型定義もdefineComponentを使うものへ変わりました。

declare module "*.vue" {
  import { defineComponent } from "vue";
  const component: ReturnType<typeof defineComponent>;
  export default component;
}

従来の型定義はcreateAppの引数として使えないので注意してください。

参考: vue-next(Vue.js 3.0 wip)+ TypeScript + webpackでの開発環境を構築する - Qiita

TypeScriptで配列からnullを取り除く

結論

const a = [1, 2, 3, null];
// nullとundefinedをはじく
const filtered = a.filter(<T>(n: T): n is NonNullable<T> => n != null);
// nullだけはじく
const filtered2 = a.filter(<T>(n: T): n is Exclude<T, null> => n !== null);

nullを除いた配列の型

nullを許容する配列があったとします。この配列にfilterをかけてnullをはじくコードは以下のようになります。

const a = [1, 2, 3, null];
const filtered = a.filter(n => n != null);

しかし、これだけではfilteredの型は(number | null)[]になります。filteredの型をnumber[]型にするにはasかisを使います。

const filtered = a.filter(n => n != null) as number[];
const filtered = a.filter((n): n is number => n != null);

これだけでもある程度は十分です。しかし、オブジェクトの配列であった場合、これだけでは不十分な場合があります。

type Obj = {
  hoge: string;
  fuga: number;
};
type ExObj = Obj & { piyo: boolean };
const objs: (Obj | ExObj | null)[] = [
  { hoge: "hoge", fuga: 3 },
  { hoge: "hoge", fuga: 3, piyo: true },
  null,
];
const filtered = objs.filter((n): n is Obj => n != null); // Obj[]型になって情報が失われる!

というわけで、もっときちんと型をつけましょう。

コード解説

const a = [1, 2, 3, null];
const filtered = a.filter(<T>(n: T): n is Exclude<T, null> => n != null);

ExcludeはUnion型Tを受け取り、TからUを除いたUnion型を返します。たとえばExclude<string | number | null, null>string | numberです。NonNullable<t>はTからnullとundefinedを除いた型を返します。

Exclude、NonNullableを使っているため、コードの意味もnullを除いたUnion型の手書きよりハッキリしています。

参考

参考というか、このページをもとに改良したのがこの記事です。

lower_bound、upper_boundで個数を数える

条件 コード
xより大きい v.end() - upper_bound(v.begin(), v.end(), x)
x以上 v.end() - lower_bound(v.begin(), v.end(), x)
xである upper_bound(v.begin(), v.end(), x) - lower_bound(v.begin(), v.end(), x)
x以下 upper_bound(v.begin(), v.end(), x) - v.begin()
x未満 lower_bound(v.begin(), v.end(), x) - v.begin()

書いておいてなんですが、「xより小さい」個数と「ちょうどxである」個数が求まれば、引き算と足し算で残りは求められるので表にするほどではないですね。