Panda Noir

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

denite.nvimからfzf.vimに移行してみた

tl;dr

  • 操作性が良い
  • :Agコマンドが強すぎる
  • めっちゃカッコイイ!!

fzf.vimとは?

fzf.vimとはfzfを使ってファイルオープンやバッファ切り替えを行うツールです。denite.nvimと役割はかなり近いです。

fzf.vimの使い方

requirement

fzf本体を別途インストールする必要があります。公式ではホームディレクトリにインストールしていますが、$XDG_CACHE_HOME以下にインストールするとホームディレクトリが汚れなくて快適です。

$ git clone https://github.com/junegunn/fzf "$XDG_CACHE_HOME/fzf"
$ $XDG_CACHE_HOME/fzf/install --xdg --no-key-bindings --completion --no-update-rc

オプションはお好みで。

Gitを使う方法以外に、vimのプラグインとして、zshのプラグインとして管理する方法もあります(しかし結局、上の方法が一番問題が起こりづらいです)。

fzf.vimのインストール

fzf.vimをインストールします。以下ではdein.nvimを例に挙げています。その他の環境の場合はfzf.vimをご覧ください。

[[plugins]]
repo = 'junegunn/fzf.vim'
on_cmd = [
    'Files',
    'ProjectFiles',
    'Buffers',
    'BLines',
    'History',
    'Tags',
    'BTags',
    'GFiles',
    'Ag',
]
hook_add = '''
nnoremap <silent> ,a :<C-u>Ag<CR>
nnoremap <silent> ,f :<C-u>ProjectFiles<CR>
nnoremap <silent> ,b :<C-u>Buffers<CR>
nnoremap <silent> ,m :<C-u>History<CR>
set rtp+=$XDG_CACHE_HOME/fzf "$XDG_CACHE_HOMEの下にインストールした場合
" set rtp+=~/.fzf "~/.fzfにインストールした場合
'''

基本設定はこれでOKです。以下はかっこよくしたり、プロジェクトディレクトリで開くためのコードです。

hook_source = '''
function! s:find_git_root()
  " プロジェクトルートで開く
  return system('git rev-parse --show-toplevel 2> /dev/null')[:-2]
endfunction

command! ProjectFiles execute 'Files' s:find_git_root()
" command! -bang -nargs=? -complete=dir Files
"     \ call fzf#vim#files(<q-args>, fzf#vim#with_preview(), <bang>0)
command! -bang -nargs=? -complete=dir Files
    \ call fzf#vim#files(<q-args>, {'options': ['--layout=reverse', '--info=inline', '--preview', 'head -20 {}']}, <bang>0)



" Terminal buffer options for fzf
autocmd! FileType fzf
autocmd  FileType fzf set noshowmode noruler nonu

" 見た目をいい感じにする
" 参考: https://github.com/junegunn/dotfiles/blob/master/vimrc
"   https://github.com/junegunn/dotfiles/blob/master/vimrc
if has('nvim')
  function! s:create_float(hl, opts)
    let buf = nvim_create_buf(v:false, v:true)
    let opts = extend({'relative': 'editor', 'style': 'minimal'}, a:opts)
    let win = nvim_open_win(buf, v:true, opts)
    call setwinvar(win, '&winhighlight', 'NormalFloat:'.a:hl)
    call setwinvar(win, '&colorcolumn', '')
    return buf
  endfunction

  function! FloatingFZF()
    " Size and position
    let width = float2nr(&columns * 0.9)
    let height = float2nr(&lines * 0.6)
    let row = float2nr((&lines - height) / 2)
    let col = float2nr((&columns - width) / 2)

    " Border
    let top = '╭' . repeat('─', width - 2) . '╮'
    let mid = '│' . repeat(' ', width - 2) . '│'
    let bot = '╰' . repeat('─', width - 2) . '╯'
    let border = [top] + repeat([mid], height - 2) + [bot]

    " Draw frame
    let s:frame = s:create_float('Comment', {'row': row, 'col': col, 'width': width, 'height': height})
    call nvim_buf_set_lines(s:frame, 0, -1, v:true, border)

    " Draw viewport
    call s:create_float('Normal', {'row': row + 1, 'col': col + 2, 'width': width - 4, 'height': height - 2})
    autocmd BufWipeout <buffer> execute 'bwipeout' s:frame
  endfunction

  let g:fzf_layout = { 'window': 'call FloatingFZF()' }
endif
'''

使い方

:Filesまたは,fと入力するとカレントディレクトリのファイルが列挙された状態になります。エンターを押すと選択したファイルを新しいバッファーで開きます。カーソルはC-nC-pで候補を移動でき、<TAB>を押すと複数選択できます。

:Buffersは開いているバッファーの一覧を表示します。こちらは複数選択はできません。

agコマンドがインストールされている場合、:Agコマンドが利用できます。こちらはag '^(?=.)'の検索結果(カレントディレクトリ以下のすべてのファイルの内容)に対してfzfでフィルタリングするというものになっています。非常に動作が軽くて快適です。エンターを押すと該当ラインにカーソルが当たった状態でバッファーを開きます。

denite.nvimとfzf.vimの違い

PROS CONS
denite.nvim ・ ソースが豊富
・ registerを扱える
・ フィルタリングのレスポンスがfzfより悪い
fzf ・ 操作性が良い
:Agが便利
・ 動作が軽い
・ denite.nvimのregisterにあたるものがデフォルトではない

こんな感じです。フィルタリングがサクサクできるのはfzf.vimの大きなメリットだと思います。

NerdFontがうまく効かないときの対処法

半年くらい悩んでいた問題が解消できたのでまとめです

環境はUbuntu 19.10を想定しています。

1. そもそもフォント選択できていますか?

そもそもターミナルの設定でフォントをきちんと設定できていない可能性があります。正しくインストールして、ちゃんと選択されていますか?nerd fontを選べていない・等幅フォントを選べていないなどよくあります。

ターミナルエミュレータがフォントを認識できていない場合は、さらに細かいトラブルシューティングが必要です。fc-cacheする、きちんと認識されるディレクトリに入れる、fontrc(なんて奴だっけ?調べて書き直す)を設定するなどなど。詳しくは調べてください(ここで書ききれません)。

もしGNOME terminal(標準のターミナル)を使っている場合、インストールした nerd fontは認識されません。システム全体のmonoフォントを変える必要があります。GNOME Tweaksを使うとシステムのフォント設定を変えられます。システムのフォントを変えたくなければ、ターミナルエミュレータを変えましょう*1

2. アンチエイリアスを切りましょう

僕はこれができていなくてずっと悩んでいました。GNOME Tweaksのフォントの設定で、アンチエイリアスが「サブピクセル」になっていると、アンチエイリアスが有効になってしまい、下の図のように縦線が表示されます。

f:id:panda_noir:20200205182430p:plain

そのため、アンチエイリアスを切るか、「標準」を選択してください。

3. fc-cacheで更新する

Nerd fontを入れたあとは~/.config/fontconfig/fonts.confをいじらなければならない場合があります(もう正直この辺はグチャグチャ試行錯誤してた段階だったのでよくわかってません)。うまく更新していないとここの設定が反映されません。きちんとfc-cache -vを実行しましょう。

*1:デフォルトのターミナルは他のものと比べてかなりショボいので、これを機会に乗り換えをオススメします。個人的オススメはXfce4 terminalです

tmuxのプラグイン「tmux-respawn-all-panes」を公開しました

卒論ちょっと進んだマン。ドラフトの締切まであと2日…つらい…

昨日書いたこちらの記事の内容をプラグイン化してみました。

tmuxのすべてのpaneでzshrcの再読み込みをする - Panda Noir

GitHub - pandanoir/tmux-respawn-all-panes: respawn all panes with one key.

どういうプラグイン?

詳しくは昨日書いた記事をご覧ください。簡単に言うと、「デタッチ済みのセッションも含めた、すべてのpaneを再生成する」プラグインです。いちいちsource ~/.zshrcするのが面倒なときに便利です。

tmuxのすべてのpaneでzshrcの再読み込みをする - Panda Noir

tpmを使っている場合、以下を書くだけでインストールできます。

set -g @plugin "pandanoir/tmux-respawn-all-panes"

あとはPrefix - Rを押すだけで再起動できます。簡単ですね。

tmuxプラグインの作り方

tpmリポジトリにすでにわかりやすい解説が書いてあるので、ぜひ読んでみてください。

github.com

終わりに

とても簡単にプラグインが作れたのでビックリしました。すごいですね…x

tmuxのすべてのpaneでzshrcの再読み込みをする

卒論が全然終わらないしストレスがすごいです…はぁ…

とりあえず息抜きにこないだ作ったtmuxの設定を上げておきます。

モチベーション

zshrcを書き直したあとに、一括ですべてのpaneに変更を適用したいからです。いちいち全てのpaneに移動してsource ~/.config/zsh/.zshrcして回るのは怠いですよね?かといって# tmux kill-serverでいちいちtmuxを落とすのもスタイリッシュではありません*1

というわけで作ったのが下の設定です。

コード

適当なファイル名をつけて、次のファイルを保存してください(僕は~/.config/tmux/respawnに保存しています)

#!/bin/bash
# カレントディレクトリが変わらないようにしつつ、すべてのpaneに対してrespawn-paneする
tmux list-panes -as -F '#{pane_id} : #{pane_current_path}' | sed -e 's/ : /\n/' | xargs -n 2 bash -c 'tmux respawn-pane -c $1 -t $0 -k $SHELL'

そして、tmux.confに以下を追記してください。

bind-key R run-shell 'bash $XDG_CONFIG_HOME/tmux/respawn' # すべてのpaneをrespawnする

Prefix - Rを入力すると、すべてのpaneのシェルが再起動します。

注意点

respawn-paneは起動しているシェルをkillします。そのため、何かしら走らせた状態ではゼッタイに実行しないでください。僕は一切の責任を取りません。

動作原理

まず概要を話すと、

  1. 全てのpaneの、idとカレントディレクトリを列挙
  2. 取得したidとカレントディレクトリを使ってrespawn-paneをする

これだけです。

1. 全paneのidとカレントディレクトリを列挙

tmux list-panes -as -F '#{pane_id} : #{pane_current_path}'の部分です。これを実行すると、たとえば次のような感じになります。

%0 : /home/pandanoir/hoge
%1 : /home/pandanoir/Documents/sotsuron-tsurai

さらに、xargsに渡して良しなにやるために、idとディレクトリパスのあいだで改行します(| sed -e 's/ : /\n/'の部分です)。tmux単体ではidとcurrent pathのあいだに改行ができなかったので仕方なくsedを使っています…

%0
/home/pandanoir/hoge
%1
/home/pandanoir/Documents/soturon-turai

2. すべてのpaneに対してrespawn-paneする

これを| xargs -n 2 bash -c "SCRIPT"につなげてやると、SCRIPTが $0に#{pane_id}、$1に#{pane_current_path}が格納された状態で実行されます。つまり、tmux respawn-pane -c #{pane_current_path} -t #{pane_id} -k $SHELLとなります*2

終わりに

要は、カレントディレクトリが変わらないようにしつつ、respawn-paneをすべてのpaneに対して実行しているだけです。bash scriptだから可読性が著しく悪いだけで、文章にすると明瞭ですね。

作ってて思いましたが、需要ありそうだし普通にpluginとして公開しようかな…卒論が終わったら

*1:僕はかれこれ3年くらい思考停止でkill-serverしてました

*2:-tはどのpaneをrespawnするか指定するオプション、-cオプションはカレントディレクトリを指定するオプションです

Reactのリハビリがてらアプリ作った記録

卒業研究とか部活に追われてあまりReactとか触れていなかったのでリハビリのつもりでやった。1日でガーッと書いたときの記録。コケたところとかも(どうせ後でまた同じところでコケるので)記録しておく。

どういうアプリ?

graphemesplitという、Unicodeで1文字をカウントするライブラリがある。それを使って文字数をカウントするだけのアプリ。ロジック部分はほぼないに等しいので、主にビューを作り込んでいた。

文字数カウント

作ってるとき思ったこと

今回はファイル分割すらせず1ファイルに全て書いた。70行以下に収まっているし、これくらいなら分割するほうが面倒。このファイル、かなりまとまっているし、結構あとあとまでテンプレートとして使いまわせそう。

流れとか

  1. 前につくったプロジェクトからwebpack.config.jsだとか色々引っ張ってくる
  2. それを眺めながら適当に叩いて直していく
  3. 形ができてきたらTypeScriptを導入(本当にビューしか書いていないので、React.FCくらいしか型が出てこないけど)
  4. バンドルサイズが大きすぎるからPreactを導入してみようと試みる

Preact導入

流れ見てもらえばわかるけど、Reactで書いた後にPreactを導入することにしたので、Reactから移行する方法について調べた。

Getting Started – Preact

どうやらpreactをインストールして、webpack.config.jsのresolve.aliasに設定するだけでいいみたい。TypeScriptの型もそれで通った。不要になったのでreactreact-domをアンインストール。

コケた部分

TypeScript導入したらビルドできなくなった

これはめっちゃカンタンに解決できた。

import React from 'react' -> import * as React from 'react'

これだけ。

styled-componentsが効かない

styled-componentsはclassを振るので、それを受け取っていないと効かない

// NG
// const Component = (props) => <h1>{props.value}</h1>;

// OK
const Component = (props) => <h1 className={props.className}>{props.value}</h1>;
const StyledComponent = styled(Component)``;

設定ファイルどれを書けばいいんだ…

awesome-typescript-loaderを導入している場合、babelrcとかは要らなくて、webpack.config.jstsconfig.jsonだけあれば良い。babel関連はtsconfig.jsonに書いて、webpack.config.jsはその辺を気にしなくてよい。

input[type=checkbox]ってどうReactで扱えばいいんだ?

まあ何にも考えずに書くならこんな感じでOK

const CheckboxComponent : React.FC<{}> = () => {
    const [flag, setFlag] = useState<boolean>(true);
    return (
        <label>
            <input type="checkbox" checked={flag}
                onChange={() => setFlag(v => !v)}/>
            フラグ{flag ? 'onだよ!' : 'offだよ!'}
        </label>
    );
};

もうちょい捻るとこんな感じ

type Props = {
    value: boolean,
    onChange: (events: React.ChangeEvent<HTMLInputElement>) => void,
};
// 外部からonChangeとvalueを受け取る
const CheckboxComponent : React.FC<Props> = ({value, onChange}) => {
    const [flag, setFlag] = useState<boolean>(true);
    return (
        <label>
            <input type="checkbox" checked={value}
                onChange={onChange}/>
            フラグ{flag ? 'onだよ!' : 'offだよ!'}
        </label>
    );
};

イベントハンドラの型わかんねえ…

まず、関数の型はこんな風に書ける。

type Props = {
    value: string,
    onChange: (e: React.ChangeEvent<HTMLTextAreaElement>) => void,
};

型についてはVSCodeとかだとマウスオーバーすると出てくる。ありがたい時代になったものだな…Vimにもこの機能欲しいしちょっと調べるか

依存関係

  • @babel/core
  • awesome-typescript-loader
  • webpack
  • webpack-cli
  • typescript
  • @types/react
  • @types/react-dom

  • preact

  • styled-components

これだけで出来るんだな…すごい

一発ネタだし、今回は特にテストとか書くつもりはない。

リハビリがてらやったけど、結構サクサクと書けて楽しかった。