DGX Spark 互換機でデスクトップを無効化する。
現状確認
systemctl get-default
# 結果が graphical.target であることを確認デスクトップを無効化する
sudo systemctl set-default multi-user.target再起動して確認
sudo reboot以上。
最近、ローカル LLM を使ってコード補完する Vim プラグインを作りました。以下はその概要と使用方法です。
Download Ollama on Windows の手順通りにインストールします。
Ollama のライブラリに Qwen2.5-14B-Instruct があるので、以下のコマンドでインストール・実行します。
なんとこれだけ。
実行で来たら対話を試みましょう。
>>> あなたが動いているプログラムと、自身のモデルを教えてください。
私はアリババクラウドによって作成された大規模な言語モデルです。私の名前はQwenで、ユーザーに多様な情報を提供し、さまざ
まな質問やタスクに対するサポートを提供することを目指しています。ただし、具体的なソフトウェアの実装詳細や内部構造につ
いては開示されていません。ユーザーの皆さまに対しては、幅広い情報提供とタスク支援を行うことを目的としています。はい、動いていそうです。
run している間は API が立ち上がっているので、Vim プラグインからもアクセスできます。 そんなわけで、ローカル LLM を使ってコード補完する Vim プラグインを作ることにしました。
そうしてできたのが mikoto2000/ollama-codeassist.vim: ローカル LLM を用いたコード補完プラグイン です。 Vim でコード補完する際に、ローカル LLM を呼び出して補完候補を生成します。
このプラグインは、Vim でコード補完する際に、ローカル LLM を呼び出して補完候補を生成します。 補完候補は、現在のカーソル行の前後のコードをコンテキストとして LLM に渡し、LLM が生成したコードを補完候補として挿入します。
基本的には、Vim meets Local LLM: Edit Text beyond the Speed of Thought - YouTube の前半部分を基に作成しました。
後半のバーチャルテキスト的なところは未実装です。
動作としてはこんな感じ。
とりあえず何らかのそれっぽいコードが挿入されているのがわかるかと思います。
主処理は 100 行にも満たない程度なので、どかっと現状のコードを張り付けておきます。
vim9script
var host = get(g:, 'ollama_codeassist_host', 'localhost')
var port = get(g:, 'ollama_codeassist_port', 11434)
var path = get(g:, 'ollama_codeassist_path', '/api/generate')
var model = get(g:, 'ollama_codeassist_model', 'qwen2.5-coder:14b')
# qwen2.5:14b-instruct
# codellama:13b
var endpoint_url = $"http://{host}:{port}{path}"
const AsyncHTTP = vital#ollamacodeassist#import("Web.AsyncHTTP")
const data_template = {
"model": model,
"prompt": null,
"suffix": null,
"stream": false,
"options": {
"stop": ["<|FIM_START|>", "<|FIM_STOP|>", "<|im_start|>", "<|im_end|>"],
}
}
var data = copy(data_template)
var line = 0
var language = 'unknown'
def UserCb(response: any)
if response.status == 200
# コンテキストの行に、レスポンスの内容を挿入する
var s = json_decode(response.content).response
# もし \n が文字として入ってくる場合も吸収(保険)
s = substitute(s, '\\n', "\n", 'g')
# もし \u0000 が文字として入ってくる場合も吸収(保険)
s = substitute(s, '\\u0000', "\%x00", 'g')
# NUL を改行に変換
s = substitute(s, "\%x00", "\n", 'g')
# 改行で行分割して挿入
var lines = split(s, '\r\?\n', 1)
setline(line, lines[0])
append(line, lines[1 : -1])
else
#echomsg response.status
endif
enddef
# 現在のバッファからコンテキストを作成する関数
def CreateCurrentBufferContext()
# 現在の行番号を取得
line = line('.')
# 現在のバッファから言語を推測
if &filetype != ''
language = &filetype
endif
# バッファ全体の内容を取得
var buffer_prefix = $"// language: {language}\n" .. $"// Please only codes, and not output codeblock text.\n// Don't add closing brackets if they already exist in suffix.\n" .. join(getline(1, '.'), '\n')
var buffer_suffix = '<|FIM_STOP|>' .. join(getline('.', '$'), '\n')
data.prompt = buffer_prefix
data.suffix = buffer_suffix
enddef
# コンテキストを基に、コード補完のリクエストを送る関数
def RequestInner()
AsyncHTTP.request({
\ 'method': 'POST',
\ 'url': endpoint_url,
\ 'data': json_encode(data),
\ 'userCallback': function('UserCb'),
\ })
enddef
export def Request()
CreateCurrentBufferContext()
RequestInner()
enddef
API リクエストは Vital.Web.AsyncHTTP に丸投げし、Ollama の API にリクエストを送っています。
ちょっと非同期リクエストを生かせない構造になっているのが課題ですが、まあとりあえず動いているのでいいかなと思っています。
以上。作ったモノの報告でした。
Windows 11 Pro と Ollama で Qwen2.5-14B-Instruct を動かす手順を説明します。
Download Ollama on Windows の手順通りにインストールします。
Ollama のライブラリに Qwen2.5-14B-Instruct があるので、以下のコマンドでインストール・実行します。
なんとこれだけ。
実行で来たら対話を試みましょう。
>>> あなたが動いているプログラムと、自身のモデルを教えてください。
私はアリババクラウドによって作成された大規模な言語モデルです。私の名前はQwenで、ユーザーに多様な情報を提供し、さまざ
まな質問やタスクに対するサポートを提供することを目指しています。ただし、具体的なソフトウェアの実装詳細や内部構造につ
いては開示されていません。ユーザーの皆さまに対しては、幅広い情報提供とタスク支援を行うことを目的としています。使い道が思いつかないけどとりあえずローカル LLM が動きました。
OS: Ubuntu24.04 on WSL2 on Windows 11 Pro Vim: 9.1.2011
Windows Terminal と wstty(putty) で試したんですが、これらターミナルは OSC52 のコピー機能をサポートしているのに対し、ペースト機能はサポートしていないようです。
そして、ペースト機能をサポートしていないターミナルでは、 Vim 内で完結するペースト操作も使用できなくなってしまいう用でした。
Vimrc に以下の設定を追加します。
if !has('win32')
if has("patch-9.1.1984")
function! InitOsc52() abort
let v:clipproviders["osc52"] = {
\ "available": v:true,
\ "copy": {
\ "*": function('osc52#Copy'),
\ "+": function('osc52#Copy')
\ },
\ }
endfunction
augroup init_osc52
autocmd!
autocmd VimEnter * call InitOsc52()
augroup END
packadd osc52
let g:osc52_force_avail = v:true
let g:osc52_disable_paste = v:true
set clipmethod=osc52,x11,wayland
endif
endif
v:clipproviders["osc52"]
がキモなのですが、paste
の設定を省略することで、ペースト機能を無効化しています。
ただし、 Vimrc
のトップレベルにべた書きで定義すると、プラグインの初期設定で上書きされてしまうため、VimEnter
イベントで初期化するようにしています。
こうすることで、何らかのタイミングがかみ合い、ペースト機能が有効になってしまうことを防いでいます。
Windows のターミナルで OSC52 のコピー機能のみを有効にしたい場合は、試してみてください。
本記事は次のソースを NotebookLM に取り込み生成したものを微調整した記事です。 Vim9 チートシート的なものが欲しかったのですが、思ったような品質まで上げるには結構な手間がかかりそうだったので諦めました。
その途中経過をおすそ分けです。
自分も Vim9 script 歴が浅いので、内容に誤りがある可能性があります。もし誤りを見つけた場合は、コメントやフィードバックをいただけると幸いです。
2010年、Vim 7.2をベースに執筆された伝説的な記事「Vimスクリプト基礎文法最速マスター - 永遠に未完成」は、多くのVim使いにとってのバイブルでした。しかし、現在のVimは「Vim9 script」という劇的な進化を遂げています。
本稿の目的は、Vim 7.2時代のレガシーな記述を卒業し、より高速で厳格な現代的パラダイムへと最短で移行することです。
Vim9 scriptを記述するには、ファイルの先頭に必ず vim9script 宣言が必要です。
vim9script
var count = 0
専門テクニカルライターの視点: 従来のVim
scriptでは、スクリプトローカルな変数や関数に s:
接頭辞を付けるのが通例でした。Vim9
scriptでは、この宣言があるファイル内ではデフォルトでスクリプトローカルとして扱われるため、煩雑な接頭辞から解放されます。
Vim9ではコメント形式が #
に刷新され、構文上の曖昧さが解消されました。
" から行末まで。ただし、:map
系コマンドや特定の引数内ではコメントとして機能しないという「キモい制約」がありました。# を使用します。専門テクニカルライターの視点: 従来の "
は文字列リテラルと混同されやすく、特にマッピング定義などで予期せぬ挙動を招くリスクがありました。#
の導入により、他のモダンなスクリプト言語と同様の安全性と直感的な記述が可能になっています。
代入に :let
コマンドを使用していた時代は終わり、var と
const による厳格な宣言が導入されました。
| 機能 | 従来 (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
で消す必要はなく(そもそも禁止)、メモリ管理と予測可能性が向上しています。
基本的な型は継承されていますが、演算子や表記に重要な変更があります。
. でしたが、Vim9では ..
を推奨(ソース外情報)。var list = [1, 2, 3]var dict = {key: 1}
(ソース外情報:キーが英数字・ハイフン・アンダースコアのみならクォート省略可)関数参照はこの記事では省略します。
専門テクニカルライターの視点: 結合演算子に ..
が推奨されるのは、. を使用すると浮動小数点数(例:
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]。
参照と代入: list や
list[-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)操作」の章を作成しました。
辞書は、他の言語でハッシュテーブルや連想配列と呼ばれているデータ構造です。キーには文字列のみ使用できます。
var dict = {} # 空の辞書
var colors = {'red': '#ff0000', 'blue': '#0000ff'}
インデックス(ブラケット)によるアクセスのほか、ドット記法も利用可能です。
var r = colors['red'] # インデックスで参照
var b = colors.blue # ドット記法で参照
colors['red'] = 'RED' # 代入
colors.green = '#00ff00' # 新しいキーの追加代入
辞書を操作するための便利な組み込み関数が用意されています。
[[key, value], ...] の形式のリストで取得します。items()
を使うことで、キーと値を同時に取り出してループ処理ができます。
for [key, val] in items(colors)
echo key .. ': ' .. val
endfor
リストと同様、map() や filter()
を使って辞書の中身を一括操作できます。Vim9 script
ではラムダ式を使うのが一般的です。
# 全ての値を大文字に変換 (map)
map(colors, (key, val) => toupper(val))
# 特定の条件に合う要素のみ残す (filter)
filter(colors, (key, val) => key =~ '^r') # キーが 'r' で始まるものだけ
※ map() と filter()
は元の辞書を直接書き換える破壊的な操作である点に注意してください。
制御構文はより規律ある構造へと進化しました。
if 文
if condition
# 処理
elseif other_condition # else if ではなく elseif である点に注意
# 処理
endif
while 文(比較)
let i = 0
while i < 5
let i += 1
endwhile
var i = 0
while i < 5
i += 1 # varで宣言した変数は let なしで更新可能
endwhile
for 文
リストを走査します。
for e in range(3) # range() は連番リストを生成
echo e
endfor
Vim9の白眉は、従来の :function を代替する
:def です。
| 特徴 | 従来形式 (:function) | Vim9形式 (:def)(ソース外情報) |
|---|---|---|
| 引数アクセス | a:var が必須 | a: 接頭辞は不要 |
| スコープ | 関数ローカル | ブロックスコープ |
| 実行 | 逐次解釈(低速) | コンパイル(高速) |
def MyFunc(msg: string): number
var len = strlen(msg) # ブロックローカルな変数
return len
enddef
専門テクニカルライターの視点: :def
で定義された関数は、初回呼び出し時にバイトコードへコンパイルされ、静的型チェックが行われます。実行速度が劇的に向上するだけでなく、実行前にエラーを検知できる堅牢性を備えています。
ソースの時代には存在しなかった、構造化プログラミングのための最新機能です。
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この記事は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>"