Neovimのプラグインを初めて作る - REPLの操作

動機 研究で使っているソフトウェアのREPLが少し使いづらい。というのも、制御文字がそのまま表示されてしまうため、十字キーのカーソル移動やCmd + Aの行先頭移動、Cmd + Kの行削除など効かないからだ。 rlwrap を使えばこの問題を解決できるのだが、別の解決案としてNeovimのREPL支援プラグインを作ってみようと思い立った。 Neovim のターミナル機能を使ってREPLを起動し、別バッファー上で入力した文字列をREPLに送るようなプラグインを作りたい。 GitHubで検索してみると同様の機能を実現するプラグインはいくつもあるようだが(例えば、vim-slime)、プラグインを作る勉強として、自分で作ってみる。 提供する機能 使用感をvlimeと似たものにしたい。キーマップは次のようにする。 <LocalLeader>ss カーソル下の行をREPLに送る。 <LocalLeader>s 選択範囲内行の文字列をREPLに送る。 <LocalLeader>i 1行入力用のバッファを表示し、そこで書いた文字列をREPLに送る。 <LocalLeader>cd Ctrl + DをREPLに送る。 <LocalLeader>cc Ctrl + CをREPLに送る。 その他、以下のコマンドを定義する。 ReplOpen [cmd]: REPLを起動する。例えばReplOpen pythonならpythonのREPLが起動する。 [cmd]には任意のコマンドが入れられるため、REPL支援というよりターミナル支援プラグインという感じがするが、気にしないことにする。 ReplSend [string]: 文字列[string]をREPLに送る。 補足 vlimeとキーマップが被るため、vlimeを入れている人はどうするんだという事になる。 その場合、キーマップが被らないようにしたり、ftplugin下にスクリプトを書くなど色々と方法が考えられる。 ここでは一番無難そうな、「ReplOpenが呼び出された時にキーマップを登録する」という方法を採用する。 注意 普段使っているのがVimではなくNeovimなので、Neovimを使ってプラグインを書く。Vimには無い関数/機能を使うので注意。 あとVim scriptをほとんど書いたこと無いため、今回載せるコードには色々改善点があるだろう。 準備 適切なディレクトリにプラグインのディレクトリを作成。自分の環境では、packpathの1つに~/.config/nvimがあったので、 ~/.config/nvim/pack/plugins/start/に置く。プラグインのディレクトリは愚直にrepl.nvimとする そこにautoload、plugin、ftpluginディレクトリを作成する。 1 2 3 4 5 6 7 8 9 repl.nvim | +-- autoload/ | | | +-- repl.vim | +-- plugin/ | +-- repl.vim REPLの起動 autoload/repl.vimに色々関数を定義する。 まずはREPLの起動から。ウインドウを分割し、ターミナルを起動し、コマンドcmdを実行する関数は素朴には以下のように書ける。 1 2 3 4 function! repl#open(cmd) abort vnew call termopen(a:cmd) endfunction 「a:変数名とかs:変数名って何?」と初めは思ったが、これの答えは:h internal-variablesにある。 a:は関数の引数を表し、s:はこのスクリプト内の変数を表す。 ...

2021-12-25 · (updated 2022-01-01) · 4 min · 655 words

Web Audio API 備忘録

Web Audio APIで色々遊んだメモ。 サイン波を鳴らす HTMLファイルを作成。適当にindex.htmlとしておく。 1 2 3 4 5 6 7 8 9 <html> <head> <meta charset="utf-8"> </head> <body> <button id="play_btn">play</button> <script src="script.js"></script> </body> </html> script.jsは以下のようにしておく。 1 2 3 4 5 6 7 8 const audioContext = new AudioContext(); const oscillatorNode = audioContext.createOscillator(); oscillatorNode.connect(audioContext.destination); document.getElementById('play_btn') .addEventListener('click', function() { oscillatorNode.start(); }, false); これで、“play” ボタンを押すとサイン波が再生される(音量に注意)。 説明 Web Audio APIの枠組みでは、色々な音やエフェクトをノードとして繋いでいく。最終的な出力を担うノードはAppContext.destinationである。 まずAudioContextのインスタンスを作成する。このContextというのはプログラミングをやっていると色々なところで現れるが (Canvas要素に描画する際にもgetContextが出てくるし、WinAPIだとデバイスコンテキストハンドル、AndroidアプリのプログラミングでもContextがある)、自分は「Audioについて色々な情報を提供してくれるオブジェクト」くらいの理解をしている。 1 const audioContext = new AudioContext(); 続いて、音源としてOscillatorNodeを作成し、繋いでやる。 1 2 const oscillatorNode = audioContext.createOscillator(); oscillatorNode.connect(audioContext.destination); oscillatorNode.start()で実際に音源が再生される。今回はclickイベントが起こったときに再生されるようにしている。 (補足) ページ読み込み時の再生はできないという仕様 Chrome/Safariの場合、以下のようにページを開いた瞬間に音がなるようなコードを書いたとしても、実際には鳴らない。 1 2 3 4 const audioContext = new AudioContext(); const oscillatorNode = audioContext.createOscillator(); oscillatorNode.connect(audioContext.destination); oscillatorNode.start(); これは、「ボタンが押す」のような、ユーザーが明示的な操作をしない限り再生されない仕様となっているかららしい。 実際 Chrome の場合、上のコードを書くと以下の警告がDevalopper Toolで表示される。 ...

2021-12-24 · (updated 2021-12-24) · 6 min · 1165 words

Pythonを使った(静的)ページの画像のURL取得

Webページの画像だけを手っ取り早く取得したい場合にどうすれば良いのかを考えた。 これを行うプログラムをPythonで取得する。 この記事で作成したプログラムはGitHubのRepositoryに公開した。 前提 Pythonのバージョンは3.10を想定。 この記事では外部ライブラリとして Requests 2.26.0 Beautiful Soup 4.10.0 tqdm 4.62.3 w3lib 1.22.0 を使う。この記事のコードを動かす場合はpipコマンドなどでインストールしておく。 方針 やることは案外単純である。 WebページのHTMLデータを取ってくる。 img要素を探して、そのsrc属性を取ってくる。 scheme、netlocが無かったらそれを付加して、完全なURLにする。 1はRequests、2はBeautiful Soupを使えば良いだろう。 3は思ったより複雑である。src属性に入っているパスには、 URL: http://foo.org/bar/hoge.png スキームが省略されている: //foo.org/bar/hoge.png 絶対パス: /bar/hoge.png 相対パス: ../bar/hoge.png データURL: data:image/png;base64,... など色々ある。 これらのフォーマットを統一して完全なURLにするのは面倒であるが、幸運にもurllib.parse.urljoinという関数があったのでこれを使う (余談: 初め、urljoinの存在を知らずに自前でURLの変換機能を実装してしまった。学びにはなったが時間を費やした…)。 ついでの機能として、「特定の要素の中に含まれているimg要素のURLを取得する」ことも考える。 これはCSSセレクタとして指定できるようにする。 まとめると、画像のURLを取得する関数は以下のようなインターフェースとなる。 1 2 def get_img_urls(url: str, selector: Optional[str]=None) -> list[str]: pass # これから実装する URLとセレクタを引数にとり、img要素のURLのリストを返す関数である。 ついでに画像ダウンロードのためのCLIや、画像を閲覧するWebアプリなどが作れたら良い。 プロジェクトの構造 Pythonでモジュールを作ったことがないため、正しい作り方が分からないが、とりあえず以下のような構成にしてみる。 細かいディレクトリの構成は各節で述べる。 1 2 3 4 5 6 7 8 9 10 11 12 /project | +--+ getimg/ | +--+ commandline/ | +--+ viewer/ | +--+ tests/ +-- __init__.py +-- test_getimg.py +-- test_commandline.py CLI CLIの書式は以下のようにする。取得したい画像のあるページのURL、及び画像のダウンロード先を指定する。 ...

2021-12-09 · (updated 2021-12-16) · 8 min · 1516 words

neovimのプラグインがうまく動かなかったので原因を探した話

(2021/12/25追記) この記事で話題にした問題は最新のddc-nvim-lspで修正されている。こちらのissue及びこちらのcommitを参照。もっとも、この記事を書いてから大分経ったため、ddc_nvim_lsp.luaのソースコードも今では大分変わっている。 以下の文章のまとめ バージョン違いには注意する ddc-nvim-lspは2021/10/1時点では、neovim 0.5.0を想定して作られているプラグインである。しかし自分はneovim 0.5.1を使ってしまっていた。neovim 0.5.1からlsp handlerの引数に破壊的変更があったため、LSPの補完が効かなかった。 究明に当たってDockerを触ったり、Luaを触ったり、ドキュメントを漁ったりして色々糧にはなったので、記録しておく。 何が起きたのか まず、プラグインの管理にはShougo/dein.vimを使った。 neovimのbuildin LSPを使ってLSPが使える環境を構築した。設定に当たって以下のプラグインを導入した。 neovim/nvim-lspconfig 入力補完はShougo/ddc.vimを使った。それにあたって以下のプラグインを導入した。 vim-denops/denops.vim: ddc.vimがDenoの機能を使うため必要。 Shougo/ddc-matcher_head Shougo/ddc-sorter_rank Shougo/ddc-around Shougo/ddc-nvim-lsp 最後のddc-nvim-lspがうまく動かなかった. Language Serverとしてpyrightを導入したのだが、実際にPythonのファイルで入力補完を試したところ,ddc-aroundの補完は反応するが,ddc-nvim-lspの補完候補が現れなかった。 Dockerを使って再現性を検証する まず、 何か他のプラグインが邪魔しているのではないか Macという環境だから問題なのだろうか という仮説を立てた。そのためには、何も無い素のneovimの環境を作る必要があると考えた。そこで、環境をDockerで構築しようと考えた。 Docker環境の構築 適当なディレクトリを作って、そこにDockerfileとdocker-compose.ymlを作成する。 Dockerfileを以下のようにする。ベースイメージはanatolelucet/neovimにした。この時点でdeinを導入する。コマンドはdeinのQuick startを参照した。deinのインストールにあたってcurl、gitコマンドが必要なので、ここで導入する。 1 2 3 FROM anatolelucet/neovim:stable-ubuntu RUN apt-get update && apt-get install -y curl git RUN curl https://raw.githubusercontent.com/Shougo/dein.vim/master/bin/installer.sh > installer.sh && sh ./installer.sh ~/.cache/dein neovimの設定ファイルはコンテナ外で編集できるようにしておく。同ディレクトリにディレクトリ.config/nvim/を作成し、その上で、docker-compose.ymlを以下のようにする。 1 2 3 4 5 6 7 8 version: '3' services: nvim: build: . volumes: - .config:/root/.config entrypoint: 'bash' working_dir: /root .config/nvim/の中にinit.vim、dein.toml'、'dein_lazy.tomlを作成。init.vimは以下の通り。 ...

2021-10-01 · (updated 2021-12-15) · 3 min · 586 words

Elmでテトリスを作った話

Elmでテトリスを作った。この記事では実装にあたって考えたポイントをメモしておく。 コードは説明のために断片的に載せる。 製作物 ここで遊べる. Repositryはこちら。 実装しなければいけない処理 大まかに作らなければいけないのは以下の処理. ボード・テトリミノのデータ構造 テトリミノの出現・回転・落下・固定 テトリミノの衝突判定 ラインがそろった時に消滅する処理 ゲームオーバー処理 キー操作 画面描画 この中からいくつかの項目について説明する。 ボードのデータ構造 ボードの落とす場は10x20のブロックで構成されている。 壁をボードに含めるかどうか、上部にマージンを設けるかどうかで、実際のボードサイズは変わる。 まず、ボードのブロックをセルと呼ぶことにする。セルを次のように定義する。Colorは適当に定義しておく。 1 2 3 type Cell = Block Color | Empty このセルを使ってボードを定義したいが、悩ましい選択が現れる。 セルを要素に持つList。セルの座標はリストの添字で判断する。 (座標, セル)を要素に持つList。 キーを座標、値をセルとしたDict。 ListをArrayにした実装も考えられる。参考までに、3つは以下のように定義できる。 1 2 3 4 5 6 7 8 type alias Board = List Cell type alias Board = List { pos : Vec Int , cell : Cell } type alias Board = Dict (Vec Int) Cell ボードのデータ構造によって諸々の関数の実装方法が大きく変わってくるので、どれを選ぶか慎重になる必要がある。 2つ目と3つ目のデータ構造はelm-gamesのRepositoryに載っているテトリスのコードから発見した。 それらのコードを見つけた時にはすでに1番目で作ってしまっていたので、現状の自分の実装は1番目のものである。 TEAのView関数としての扱いやすさを考えるなら、2番目の実装が一番良いと思う。例えばセルの描画関数をviewCellとして、viewBoardは次のように書ける。 1 2 3 4 5 6 7 import Svg viewBoard : Board -> Svg Msg viewBoard board = Svg.g [] (List.map viewCell board) 他のデータ構造で実装する場合でも、viewBoardに渡す前に一旦{ pos, cell }のデータ構造に変換しておいた方が書きやすい。 ...

2021-08-10 · (updated 2021-08-10) · 8 min · 1571 words

線形回帰メモ 正則化

問題設定 $\bm{y} = (y^{(1)}, y^{(2)}, \ldots, y^{(N)})^T,\ \bm{x}_i = (1, x_1^{(i)}, x_2^{(i)}, \ldots, x_D^{(i)})^T$ とおく。$(\bm{x}_i, y_i),\ i = 1, 2, \ldots, N$ がデータとして与えられている。このとき、入力と出力の間に $$ \begin{aligned} y &= h_{\bm{w}}(\bm{x})\\ &:= w_0 + w_1x_1 + w_2x_2 + \cdots + w_Dx_D\\ &= \bm{w}^T\bm{x} \end{aligned} $$ が成り立つと仮定し、これに適する$\bm{w}$を見つけたい。 (正則化前の)コスト関数 ここで「適する」とは具体的に何なのかというと、ここでは予測とデータとの二乗誤差の和 $$ J(\bm{w}) = \frac{1}{2} \sum_{i=1}^{N} (h_{\bm{w}}(\bm{x}_i) - y^{(i)})^2 $$ が最小となる $\bm{w}$ を求める。この $J$ をここではコスト関数と呼ぶ。 係数 $1/2$ は微分した時に出てくる $2$ を消し去るための便宜的なものであり、つける必然はない。 L1正則化とL2正則化 コスト関数に $\bm{w}_i$ のL1ノルム(の1乗)の項を付けることをL1正則化という。 $$ J_1(\bm{w}) = J(\bm{w}) + \lambda \|\bm{w}\|_1 $$ ...

2021-08-07 · (updated 2022-01-13) · 6 min · 1134 words

TikZ 備忘録

随時更新。だいぶ量が増えてきたので,いくつかを分割したほうが良いかもしれない. 毎回マニュアルから情報を探すのが面倒なので、基本的なものをここにまとめたい。個人的に気になったことに対しては深堀して補足しているが、細かいことを気にしすぎた結果、TikZやPGFのソースコードを読みに行く羽目になった。 またここに書いてある内容がベストプラクティスとは限らないことに注意。もっと簡単な書き方があるかもしれない。 情報の集め方 ここに載っているものはほぼTikZ/PGFのマニュアルに載っている。 TeX Liveを導入しているなら、コマンドtexdoc tikzで開くはず。これと同じものがCTANの “PGF Manual"のリンクからダウンロードできる。 非公式ではあるが、HTML版のマニュアルも公開されたようだ:The TikZ and PGF Packages この記事では、なるべく参照した情報を記載するようにする。ここに書いてあることが間違っている場合があるので、何かおかしいなと思ったらマニュアルを参照すること。 知らないキーワードや記号が出てきたらマニュアル末尾のindexで探すと良い。 TikZで出来ることを把握したいなら、Part Iのチュートリアルを読んでみるのが有効。もしくはPartIII, Vあたりを流し読みする。PGF Manualはページ数が膨大なため、全部読もうとするのは恐らく得策では無い。目次を眺めながら興味のあるところをつまむのが良いと思う(読み方について、Introductionの1.4 How to Read This Manualも参照)。 既にやりたいことがあるが、TikZで実現する方法が分からない場合は、ググる。英語のキーワードで検索すれば、大抵Stack Exchangeがヒットする。画像検索も有効。 準備 パッケージ読み込み・この記事での記法の注意 TikZはtikzパッケージから読み込める。 1 \usepakcage{tikz} 以降、コードを記載するときはこの記述を省略する。 また、TikZには色々な便利なライブラリが用意されている。例えば座標計算に便利なライブラリであるCalcは次のように読み込む。 1 \usetikzlibrary{calc} 以降、コード中で必要なライブラリがあった場合は、コードの先頭に\usetikzlibraryを記載することにする。 このコマンドは実際にはプリアンブルに書く必要があることに注意。 DVIドライバの指定 必ずクラスオプションにDVIドライバを指定すること。さもなければ、色が出力されなかったり、図形の位置が正確に計算されなかったりする。 以下は、DVIドライバをdvipdfmx、クラスをjsarticleで行う例。 1 \documentclass[dvipdfmx]{jsarticle} クラスオプションにDVIドライバを指定する必要性については、以下のサイトを参照: 日本語 LaTeX の新常識 2021 - Qiita。 色を定義 (TikZの話ではない) TikZではなくxcolorの話だが、大事なのでここで記す。これはtikzパッケージを読み込んだときに自動で読み込まれるようだが、もしxcolor単体で使いたいなら、xcolorパッケージを読み込むこと。 1 \usepakcage{xcolor} 色同士を!で混ぜることができる。例えばred!20!blue!30!whiteと書くと、赤、青、白がそれぞれ20%、30%、(100-20-30)%混ざった色になる。 カラーコードやRGB値などから定義したい場合は\definecolor、既存の色を混ぜて使いたい場合は\colorletを利用。 1 2 \definecolor{mycolor1}{HTML}{888888} # 定義する色名 カラーモデル 色の値 \colorlet{mycolor2}{orange!75!white} # 定義する色名 色 色名に-をつけると補色を表現できる。ただし、ここでの補色はRGBでの補色。例えば-redとすると赤色の補色のシアンとなる。RYBでの補色を使いたい場合は-には頼らず、色を直接\definecolorで指定する必要があると思われる。 ...

2021-07-30 · (updated 2023-01-19) · 18 min · 3832 words

ロジスティック回帰 メモ

式変形の一部はProbabilistic Machine Learning: An Introductionを参考にしている。 問題設定 データが $N$ 個あり、入力は $\bm{x}_n = (x_{n1}, x_{n2}, \ldots, x_{nD})$、出力は $y_n \in \{ 0, 1 \}$ とする。 このとき、入力 $\bm{x}$ が与えられたとき出力 $y$ を予測したい。 確率モデルの定義 ここでは確率的なモデルを考える。すなわち、 データ $\bm{x}$ が与えられたとき、 $y = 0, 1$ のどちらの確率が高いのかを考える。 $y$ は2値だから、ベルヌーイ分布としてモデル化できる。 $$ p(y ; \mu) = \mu^y (1 - \mu)^{1 - y} $$ これは、$y = 1$ である確率が $\mu$ 、$y = 0$ である確率が $1 - \mu$ であることを意味する。 $\mu$ は確率だから、$0 \le \mu \le 1$ である必要がある。 ...

2021-07-11 · (updated 2021-12-25) · 7 min · 1430 words

PureScriptで作るBrainfuckインタプリタ 4/4 Halogenの利用

Halogenの利用 続いて、GUIでBrainfuckを動かすことをやってみる。 GUIのフレームワークとして、ここではpurescript-halogenを使ってみる。 Halogenについてはまだ勉強中で、この記事は解説記事というより勉強記録である(いままでの記事もそうではあるのだが)。 % spago install halogen 雛形 src/Main.pursを以下のようにする。 ここのコードはほとんどHalogen Guideと同様。 関数名的におそらく、body要素を取得して、その中でcomponentを走らせるのだと思う。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 module Main where import Prelude import Effect (Effect) import Halogen.Aff (awaitBody, runHalogenAff) as HA import Halogen.VDom.Driver (runUI) import Component (component) main :: Effect Unit main = HA.runHalogenAff do body <- HA.awaitBody runUI component unit body componentはsrc/Component.pursで定義する。とりあえず雛形を作成。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 module Component where import Prelude import Halogen as H import Halogen.HTML as HH data Action = Dummy type State = {} initialState :: forall input. input -> State initialState _ = {} component :: forall query input output m. H.Component query input output m component = H.mkComponent { initialState , render , eval: H.mkEval $ H.defaultEval { handleAction = handleAction } } render :: forall m. State -> H.ComponentHTML Action () m render state = HH.p_ [ HH.text "Hello" ] handleAction :: forall output m. Action -> H.HalogenM State Action () output m Unit handleAction = case _ of Dummy -> pure unit Halogen Guide 02の2段落目によると、Componentとは、 「入力を受け付けて、HTMLを生成するもの」のこと。さらにComponentは内部状態を持っている。そして何かイベントを定義することができ、それに応じて内部状態を変えたり、副作用を起こしたりできる。 ...

2021-07-09 · (updated 2021-07-09) · 12 min · 2530 words

PureScriptで作るBrainfuckインタプリタ 3/4 CUIでの可視化

動作の可視化 インタプリタ動作中における内部状態を可視化できると面白い。 そこで、インタプリタ動作中のログを出力できるような仕組みを作る。 ログは以下のタイミングで起こるようにする。 onStart: インタプリタが動作したときに起こる。 onState state: 各ステップで状態を取得したときに起こる。 onCmd cmd: 各ステップで命令を取得できたときに起こる。 onEnd: インタプリタが終了するときに起こる。 これらはイベントリスナのように、関数の形で指定する。 Logの作成 src/Brainfuck/Interp/Log.pursを作成。 以下のimport文を書く。 1 2 3 4 5 6 7 8 9 module Brainfuck.Interp.Log where import Prelude import Brainfuck.Interp (Interp) import Brainfuck.State (State) import Brainfuck.Command (Command) import Effect.Class (class MonadEffect, liftEffect) import Effect.Console (log) Logを定義。 1 2 3 4 5 6 newtype Log m = Log { onStart :: Interp m Unit , onState :: State -> Interp m Unit , onCmd :: Command -> Interp m Unit , onEnd :: Interp m Unit } 関連する関数を定義。 1 2 3 4 5 6 7 8 9 10 11 12 13 14 logStart :: forall m. Log m -> Interp m Unit logStart (Log { onStart }) = onStart logState :: forall m. Log m -> State -> Interp m Unit logState (Log { onState }) = onState logCmd :: forall m. Log m -> Command -> Interp m Unit logCmd (Log { onCmd }) = onCmd logEnd :: forall m. Log m -> Interp m Unit logEnd (Log { onEnd }) = onEnd いくつかのLog mを作っておく。 ...

2021-07-06 · (updated 2021-07-09) · 12 min · 2395 words