2026年2月13日金曜日

Vim9 script 基礎文法最速マスターを AI に書かせようと頑張った

Vim9 script 基礎文法最速マスターを AI に書かせようと頑張った

本記事は次のソースを NotebookLM に取り込み生成したものを微調整した記事です。 Vim9 チートシート的なものが欲しかったのですが、思ったような品質まで上げるには結構な手間がかかりそうだったので諦めました。

その途中経過をおすそ分けです。

自分も Vim9 script 歴が浅いので、内容に誤りがある可能性があります。もし誤りを見つけた場合は、コメントやフィードバックをいただけると幸いです。

はじめに

2010年、Vim 7.2をベースに執筆された伝説的な記事「Vimスクリプト基礎文法最速マスター - 永遠に未完成」は、多くのVim使いにとってのバイブルでした。しかし、現在のVimは「Vim9 script」という劇的な進化を遂げています。

本稿の目的は、Vim 7.2時代のレガシーな記述を卒業し、より高速で厳格な現代的パラダイムへと最短で移行することです。

必須の宣言と基本構造

Vim9 scriptを記述するには、ファイルの先頭に必ず vim9script 宣言が必要です。

  • Vim9固有の仕様(ソース外情報):
vim9script

変数宣言(s: 接頭辞は不要になった)

var count = 0

専門テクニカルライターの視点: 従来のVim scriptでは、スクリプトローカルな変数や関数に s: 接頭辞を付けるのが通例でした。Vim9 scriptでは、この宣言があるファイル内ではデフォルトでスクリプトローカルとして扱われるため、煩雑な接頭辞から解放されます。

コメントの書き方

Vim9ではコメント形式が # に刷新され、構文上の曖昧さが解消されました。

  • 従来形式 : " から行末まで。ただし、:map 系コマンドや特定の引数内ではコメントとして機能しないという「キモい制約」がありました。
  • Vim9形式(ソース外情報): # を使用します。

専門テクニカルライターの視点: 従来の " は文字列リテラルと混同されやすく、特にマッピング定義などで予期せぬ挙動を招くリスクがありました。# の導入により、他のモダンなスクリプト言語と同様の安全性と直感的な記述が可能になっています。

変数宣言と代入(var, const vs let)

代入に :let コマンドを使用していた時代は終わり、varconst による厳格な宣言が導入されました。

  • 比較表
機能 従来 (Vim 7.2) Vim9 script(ソース外情報)
変数宣言 let a = 1 var a = 1
定数宣言 (なし) const b = 2
型変更 let a = 1 → let a = “s” (可能) 禁止 (コンパイルエラー)
変数削除 :unlet a 禁止 (var 変数は削除不可)

専門テクニカルライターの視点: Vim9では静的型チェックが行われるため、一度数値として宣言した変数に文字列を代入することはできません。また、var で宣言したローカル変数は :unlet で消す必要はなく(そもそも禁止)、メモリ管理と予測可能性が向上しています。

データ型とリテラル

基本的な型は継承されていますが、演算子や表記に重要な変更があります。

  • 数値: 整数と小数(Vim 7.2導入)をサポート。
  • 文字列:
    • ダブルクォート(特殊文字展開あり)とシングルクォート(展開なし)を使い分ける。
    • 結合: 従来は . でしたが、Vim9では .. を推奨(ソース外情報)。
  • リスト: var list = [1, 2, 3]
  • 辞書: var dict = {key: 1} (ソース外情報:キーが英数字・ハイフン・アンダースコアのみならクォート省略可)
  • 関数参照 (Funcref)

関数参照はこの記事では省略します。

専門テクニカルライターの視点: 結合演算子に .. が推奨されるのは、. を使用すると浮動小数点数(例: 1 . 'a')との判別が困難になるという曖昧さを排除するためです。

ソースに基づき、Vim9 scriptでの数値計算と計算時の注意点をまとめた「四則演算」の章を作成しました。

この内容は、ソースにあるVim 7.2当時の仕様 を踏まえつつ、Vim9 scriptの文法(var#)に適合させたものです。


数値・四則演算

基本演算子

一般的なプログラミング言語と同様の演算子が使用可能です。 * + (加算) * - (減算) * * (乗算) * / (除算) * % (剰余)

var a = 1 + 1  # 2
var b = 5 - 2  # 3
var c = 2 * 3  # 6
var d = 3 % 2  # 1

複合演算子

+- については、値を直接更新する複合演算子が利用可能です。

var i = 1
i += 2  # 3
i -= 1  # 2

※ソースによれば、*=/= は使用できないため注意してください。

整数と小数の割り算

Vimスクリプトの重要な仕様として、整数同士の割り算の結果は整数のまま(切り捨て)になります。 小数(Float)の結果を得たい場合は、少なくとも一方を小数として記述する必要があります。

var val1 = 3 / 2      # 1
var val2 = 3 / 2.0    # 1.5

型の暗黙的な変換

数値演算などの特定の箇所では値が暗黙的に変換されることがありますが、整数値と小数値の変換は暗黙的には行われません。変換が必要な場面で適切な型でない場合はエラーになるため、注意が必要です。

文字列操作

  • 結合: .. または join(list, sep)
  • 分割: split(str, sep)
  • 長さ: strlen(str)
  • 検索: stridx(haystack, needle)
  • スライス: str[start : end] で部分抽出が可能。
    • 'abcd'[1 : 2]'bc''abcd'[ : 1]'ab'

リスト操作

  • 基本操作: len() で長さ取得、add() で末尾追加、insert() で位置指定挿入 [3]。

  • 削除: remove(list, index) で要素を削除・取得 [3]。

  • 参照と代入: listlist[-1](末尾)で参照し、list = val で書き換え [3]。

  • 展開代入: 複数の変数に一括代入可能 [4, 5]。

    var [a, b] = [2, 6]
    var [first, second; rest] = [1-3, 6, 7] # rest は [1, 3, 7]

    ソース に基づき、Vim9 script の文法に合わせた「辞書(Dictionary)操作」の章を作成しました。


辞書 (Dictionary)

辞書は、他の言語でハッシュテーブルや連想配列と呼ばれているデータ構造です。キーには文字列のみ使用できます。

辞書の表現

var dict = {}                 # 空の辞書
var colors = {'red': '#ff0000', 'blue': '#0000ff'}

要素の参照と代入

インデックス(ブラケット)によるアクセスのほか、ドット記法も利用可能です。

var r = colors['red']         # インデックスで参照
var b = colors.blue           # ドット記法で参照

colors['red'] = 'RED'         # 代入
colors.green = '#00ff00'      # 新しいキーの追加代入

主要な操作関数

辞書を操作するための便利な組み込み関数が用意されています。

  • keys(dict): 全てのキーをリストで取得します。
  • values(dict): 全ての値をリストで取得します。
  • items(dict): 全てのキーと値を [[key, value], ...] の形式のリストで取得します。
  • has_key(dict, key): 指定したキーが存在するか確認します(真なら 1、偽なら 0)。
  • remove(dict, key): 指定したキーの要素を削除し、その値を返します。

辞書の走査 (for文)

items() を使うことで、キーと値を同時に取り出してループ処理ができます。

for [key, val] in items(colors)
  echo key .. ': ' .. val
endfor

高度な操作 (map, filter)

リストと同様、map()filter() を使って辞書の中身を一括操作できます。Vim9 script ではラムダ式を使うのが一般的です。

# 全ての値を大文字に変換 (map)
map(colors, (key, val) => toupper(val))

# 特定の条件に合う要素のみ残す (filter)
filter(colors, (key, val) => key =~ '^r') # キーが 'r' で始まるものだけ

map()filter()元の辞書を直接書き換える破壊的な操作である点に注意してください。

制御構文(if, for, while)

制御構文はより規律ある構造へと進化しました。

if 文

if condition
    # 処理
elseif other_condition  # else if ではなく elseif である点に注意
    # 処理
endif

while 文(比較)

  • 従来形式:
let i = 0
while i < 5
    let i += 1
endwhile
  • Vim9形式:
var i = 0
while i < 5
    i += 1  # varで宣言した変数は let なしで更新可能
endwhile

for 文

リストを走査します。

for e in range(3)  # range() は連番リストを生成
    echo e
endfor

関数定義(def vs function)

Vim9の白眉は、従来の :function を代替する :def です。

  • 比較コード
特徴 従来形式 (:function) Vim9形式 (:def)(ソース外情報)
引数アクセス a:var が必須 a: 接頭辞は不要
スコープ 関数ローカル ブロックスコープ
実行 逐次解釈(低速) コンパイル(高速)

Vim9の関数定義

def MyFunc(msg: string): number
    var len = strlen(msg)  # ブロックローカルな変数
    return len
enddef

専門テクニカルライターの視点: :def で定義された関数は、初回呼び出し時にバイトコードへコンパイルされ、静的型チェックが行われます。実行速度が劇的に向上するだけでなく、実行前にエラーを検知できる堅牢性を備えています。

Vim9特有の高度な機能

ソースの時代には存在しなかった、構造化プログラミングのための最新機能です。

  • クラス (class)(ソース外情報):
class Robot
    var name: string
    def Hello()
        echo "I am " .. this.name
    enddef
endclass
  • ラムダ式(ソース外情報):
var Filtered = filter([1, 2, 3], (k, v) => v > 1)
  • 可変長引数: 従来は ...a:000 でしたが、Vim9では ...name: list<type> と記述し、型安全なリストとして扱います(ソース外情報)。

実行と例外処理

  • 実行: :source script.vim または vim -S script.vim
  • 例外処理:
try
    # 処理
catch /pattern/  # 正規表現でエラーを捕捉
    # エラー処理
finally
    # 終了処理
endtry

まとめ

Vim9 scriptは、2010年当時の「コマンドの羅列」という側面を保ちつつ、レキシカルスコープや静的型付け、コンパイルといった現代的なプログラミング言語の恩恵をVimにもたらしました。

本稿で触れたのは基礎の入り口に過ぎません。Vim9の真価を理解するには、公式ドキュメントが唯一無二のガイドとなります。2010年当時からの変わらぬ鉄則を胸に、Vimをさらに深く使いこなしましょう。

「可能な限り :help を参照しましょう」

  • :help vim9
  • :help vim9-functions
  • :help vim9-classes

参考資料

2026年2月2日月曜日

ファイル名補完を「カレントディレクトリから」ではなく「開いているファイルのディレクトリから」にしたい

この記事はVim駅伝の2026-2-2 の記事です。

Vim では <C-x><C-f> で、「カレントディレクトリからのファイル名補完」ができます。

Markdown などで資料を書いていると、リンク先の記述をするために「そのファイルのディレクトリからのファイル補完」がしたくなることがあります。

ただ、資料のビルドなどがあるためカレントディレクトリは docroot から動かしたくありません。

そんな願いをかなえるスクリプトを作ったので共有します。

やり方としては、「補完開始直前に lcd でカレントディレクトリを動かして、補完完了後に元に戻す」という感じです。

同じ挙動が良いという方は以下スクリプトを使ってみてください。

" カレントディレクトリを開いているファイルのディレクトリへ移動
function! file_complete_extension#start_lcd_for_insert_completion() abort
  if exists('b:_saved_lcd')
    return ''
  endif

  " 現在のウィンドウの cwd を保存
  let b:_saved_lcd = getcwd(-1)

  " 開いているファイルのディレクトリへ移動
  let l:dir = expand('%:p:h')
  if !empty(l:dir)
    execute 'lcd' fnameescape(l:dir)
  endif
  return ''
endfunction

" CompleteDone で restore_lcd を発火させる
augroup restore_lcd_after_completion
  autocmd!
  autocmd CompleteDone * call file_complete_extension#restore_lcd()
augroup END

" b:_saved_lcd に戻る
function! file_complete_extension#restore_lcd() abort
  " b:_saved_lcd があればそこに戻る
  if exists('b:_saved_lcd') && !empty(b:_saved_lcd)
    execute 'lcd' fnameescape(b:_saved_lcd)
    " 後始末、 b:_saved_lcd を unlet
    unlet b:_saved_lcd
  endif
endfunction

inoremap <expr> <C-x><C-f>
      \ file_complete_extension#start_lcd_for_insert_completion() . "\<C-x>\<C-f>"

2025年12月28日日曜日

静的サイトジェネレーター Rspress の Quick start をやってみた

プロジェクト作成

node ➜ /workspaces/TIL/Rspress/firststep (rspress-firststep) $ npm create rspress@latest
Need to install the following packages:
create-rspress@1.47.0
Ok to proceed? (y) y


> npx
> create-rspress


  Create Rspress Project

  Project name or path
  rspress-firststep-project

  Select additional tools (Use <space> to select, <enter> to continue)
  Add Biome for code linting and formatting, Add ESLint for code linting, Add Prettier for code formatting

  Next steps ───────────────────╮

  cd rspress-firststep-project  │
  npm install                   │
  npm run dev                   │

├────────────────────────────────╯

  Done.

npm notice
npm notice New major version of npm available! 10.9.2 -> 11.7.0
npm notice Changelog: https://github.com/npm/cli/releases/tag/v11.7.0
npm notice To update run: npm install -g npm@11.7.0
npm notice

node ➜ /workspaces/TIL/Rspress/firststep (rspress-firststep) $ cd rspress-firststep-project/

以下の構成でファイルが生成される。

rspress-firststep-project
|-- README.md                       : Rspress プロジェクトの README
|-- biome.json                      : Biome 設定ファイル
|-- docs                            : ドキュメントルート
|   |-- _meta.json                  : ヘッダ情報が書いてある
|   |-- guide
|   |   |-- _meta.json
|   |   `-- index.md
|   |-- hello.md
|   |-- index.md
|   `-- public                      : 公開リソース置き場
|       |-- rspress-dark-logo.png
|       |-- rspress-icon.png
|       `-- rspress-light-logo.png
|-- eslint.config.mjs               : ESLint 設定ファイル
|-- package.json                    : npm 設定ファイル
|-- rspress.config.ts               : Rspress 設定ファイル
`-- tsconfig.json                   : TypeScript 設定ファイル

開発サーバーで実行

npm install
npm run dev

ブラウザで http://localhost:3000 にアクセスすると、生成されたページが表示される。

その他コマンド

その他、チェック、フォーマット、リント、プレビュー等のコマンドも定義されているので必要に応じて適宜使える。

node ➜ .../TIL/Rspress/firststep/rspress-firststep-project (rspress-firststep) $ npm run
Scripts available in rspress-firststep-project@1.0.0 via `npm run-script`:
  build
    rspress build
  check
    biome check --write
  dev
    rspress dev
  format
    prettier --write .
  lint
    eslint .
  preview
    rspress preview

ビルド

npm run build

doc_build に静的サイトが生成される。

Nginx にデプロイしてみる

docker 上の nginx にデプロイしてみる。

docker run -p 8081:80 -v $(pwd)/doc_build:/usr/share/nginx/html:ro nginx

ブラウザで http://localhost:8081 にアクセスすると、デプロイされたページが表示される。

参考資料