2024年4月18日木曜日

Vim の実行バイナリからその Vim のバージョンを確認したい

今使っている Vim のバージョン(e.g., 9.1.0342) を、 Vim の実行バイナリから取得する方法のメモ。

1 行で

vim -Es +"let vl=v:versionlong | put =printf('%d.%d.%04d', str2nr(vl[0]), str2nr(vl[1:2]), str2nr(vl[3:])) | print | q!"

目的

「最新リリースと比較して、新しいものがあれば再ダウンロードする」というのをやりたい。

Vim を、Windows で使ったり、 dev container に転送して使ったりしているため、ポータブルな形で配布されている Vim をよく利用しており、 利用している Vim のバージョンを更新しようとしたときに、「今ここにあるこのバイナリのバージョンはいくつだ?」をシェルスクリプトから確認したくなった。

そのためにはコマンドラインから Vim を起動し、バージョン番号を標準出力へ出力してもらわなければいけない。

ポータブルな形で配布されている Vim の例:

前提

動作確認環境は以下。

バージョン取得方法の説明

vim -Es +"let vl=v:versionlong | put =printf('%d.%d.%04d', str2nr(vl[0]), str2nr(vl[1:2]), str2nr(vl[3:])) | print | q!"
  • Vim の起動オプションの話
    • -E: Vim を「拡張 Ex モード」で起動
    • -s: サイレントモード。「Ex モード」「拡張 Ex モード」での実行時に、一部コマンドの実行結果以外を標準出力へ出力しないようにする
      • 標準出力されるコマンドは以下
        • print
        • list
        • number
        • set
  • Vim script の話
    • v:versionlong: パッチレベルを含めたバージョン文字列(9.1.0000 の場合 9010000 となる)
    • printf: C 言語と同じフォーマットで整形できる関数。前述の v:versionlong の桁を切り取ってフォーマットするのに利用
    • str2nr: 文字列を数値に変換する関数
    • put: バッファに文字を出力。 put =print...(略) で、 print の戻り値をバッファへ出力している
    • print: バッファの、現在のカーソルがある行を標準出力する
    • q!: 現在のバッファを破棄して Vim を終了する

参考資料

2024年4月16日火曜日

Vim の CUI 版に描画テストを追加して実行する

以下 3 点を忘れないようにメモ。

  • ビルド環境構築方法
  • CFLAGS へのワーニングフラグ追加
  • 一部のみテスト実行方法
  • ターミナルの描画テスト実装方法

前提

  • OS: Ubuntu 22.04

ビルド環境構築方法

ビルドパッケージ取得用に deb-src ソースを追加

後述の apt build-dep を利用するために、 /etc/apt/sources.list のコメントアウトされている deb-src を有効にする。

sed -i -e 's/^# deb-src /deb-src /' /etc/apt/sources.list

vim-gtk3 のビルドに必要なパッケージのインストール

apt build-dep を利用し、Debian が vim-gtk3 のパッケージをビルドするために使っているパッケージをインストールする。

sudo apt build-dep vim-gtk3

CFLAGS へのワーニングフラグ追加

CI では、以下のようにワーニングフラグの追加を行っているので、 Pull request を送るならローカルでもこれらフラグを追加してビルドすること。

ci.yml#L242-L248 - vim/vim

テスト実行方法

全テスト実行

src ディレクトリ内で make test する。

cd src
make test

部分的なテスト実行

src/testdirtest_*.vim が複数あるので、ファイル名の拡張子を除いたものを make に渡すと、その中のテストのみを実行できる。

cd src/testdir
make test_utf8

ターミナルの描画テスト実装方法

CUI の描画テストを行う場合には、Vim が提供している、ターミナル出力をダンプ・比較する仕組みを使う。

5. 画面ダンプの差分 - terminal - Vim日本語ドキュメント の「Vimの画面ダンプテストを書く」の通りにやればよい。

  1. ターミナルダンプの正解ファイルを生成する(最初は空ファイル)
    • src/testdir/dumps に入れる
  2. 「1.」で作ったファイルを正解ファイルとして読み込むテストを実装する
    • テストの実装は後述
  3. 「2.」で作ったテストを実行する
    • テストが失敗して、失敗時のターミナルダンプが src/testdir/failed に出力される
  4. 新しい vim を立ち上げ、失敗時のターミナルダンプを読み込み、想定通りになっているかを確認
    • vim -u NONE -N して :call term_dumpload("./failed/<「2.」で出力された失敗時のターミナルダンプ>")
  5. 「4.」の結果が想定通りであれば、失敗時のターミナルダンプで「1.」で作った正解ファイルを上書きする
    • cp failed/<「2.」で出力された失敗時のターミナルダンプ> dumps/<「1.」で作った空ファイル>

テスト実装

これも 5. 画面ダンプの差分 - terminal - Vim日本語ドキュメント の「Vimの画面ダンプテストを書く」の通りにやればよい。

例えば以下のように実装する。

func Test_setcellwidths_with_non_ambiwidth_character_dump()
  CheckRunVimInTerminal

  " テスト開始時の初期状態までもっていくためのスクリプトを定義
  let lines =<< trim END
      call setline(1, [repeat("\u279c", 60), repeat("\u279c", 60)])
      set ambiwidth=single
  END
  call writefile(lines, 'XCellwidthsWithNonAmbiwidthCharacter', 'D')

  " vim を開いてテスト開始時の初期状態までもっていく(`-S` は「Vim を起動した後に指定したファイルを Vim script として実行する」オプション)
  let buf = RunVimInTerminal('-S XCellwidthsWithNonAmbiwidthCharacter', {'rows': 6, 'cols': 50})

  " 初期状態から、「1.」で作った正解ファイルの状態になるまでの操作を実行
  call term_sendkeys(buf, ":call setcellwidths([[0x279c, 0x279c, 1]])\<CR>")
  call term_sendkeys(buf, ":echo\<CR>")

  " 「1.」で作った正解ファイルと比較する。差分があった場合テストが失敗する。
  call VerifyScreenDump(buf, 'Test_setcellwidths_with_non_ambiwidth_character_dump_1', {})
endfunc

参考資料

変更履歴

日付 内容
2024/4/16 新規作成
2024/4/17 CFLAGS へのワーニングフラグ追加について追記

2024年4月7日日曜日

Go で JSON をマージする(darccio/mergo 編)

mikoto2000/devcontainer.vim: コンテナ上で Vim を使った開発をするためのツール。 VSCode Dev Container の Vim 版を目指しています。 の中で、 JSON をマージしたいという要求が出てきたので darccio/mergo: Mergo: merging Go structs and maps since 2013 を試す。

正確に言うと、「構造体・マップをマージするライブラリ」だが、 Marshal 後の JSON でも使えるので試す。

開発環境起動

docker run -it --rm -v "$(pwd):/work" --workdir /work -v "$HOME/.vim:/root/.vim" golang:1.22.1-bookworm

環境構築

go mod init github.com/mikoto2000/golang/json/mergo/firststep
go get dario.cat/mergo

JSON ファイル作成

json/base.json:

{
  "name":"Go",
  "image":"mcr.microsoft.com/devcontainers/go:1-1.22-bookworm",
  "mounts": [
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.gitconfig",
      "target": "/home/vscode/.gitconfig"
    }
  ],
  "features":{},
  "remoteUser":"vscode"
}

json/additional.json:

{
  "mounts": [
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.vim",
      "target": "/home/vscode/.vim"
    }
  ]
}

プログラム実装

JSON のスキーマファイル定義

devcontainer/schema.go:

package devcontainer

import (
        "encoding/json"
)

type Devcontainer struct {
        Name       string
        Image      string
        Mounts     Mounts
        Features   interface{}
        RemoteUser string
}

type Mounts []Mount

type Mount struct {
        Type   string
        Source string
        Target string
}

func UnmarshalDevcontainer(data []byte) (Devcontainer, error) {
        var d Devcontainer
        err := json.Unmarshal(data, &d)
        return d, err
}

主処理実装

main.go:

package main

import (
        "fmt"
        "os"

        "dario.cat/mergo"

        "github.com/mikoto2000/golang/json/mergo/firststep/devcontainer"
)

const baseJsonPath = "json/base.json"
const additionalJsonPath = "json/additional.json"

func main() {
        baseJson, err := parseJsonFile("json/base.json")
        if err != nil {
                panic(err)
        }
        fmt.Printf("=== %s ===\n", baseJsonPath)
        fmt.Printf("%+v\n", baseJson)
        fmt.Println()

        additionalJson, err := parseJsonFile("json/additional.json")
        if err != nil {
                panic(err)
        }
        fmt.Printf("=== %s ===\n", additionalJsonPath)
        fmt.Printf("%+v\n", additionalJson)
        fmt.Println()

        // マージオプションは以下を参照
        // https://github.com/darccio/mergo/blob/cde9f0ea26cccb1168ee3900cf8ca457bb928c3c/merge.go#L329-L372
        mergo.Merge(&baseJson, additionalJson, mergo.WithAppendSlice)
        fmt.Printf("=== Merged JSON %s and %s ===\n", baseJsonPath, additionalJsonPath)
        fmt.Printf("%+v\n", baseJson)
        fmt.Println()

}

func parseJsonFile(jsonFilePath string) (devcontainer.Devcontainer, error) {
        jsonContent, err := os.ReadFile(jsonFilePath)
        if err != nil {
                return devcontainer.Devcontainer{}, err
        }

        d, err := devcontainer.UnmarshalDevcontainer(jsonContent)
        if err != nil {
                return devcontainer.Devcontainer{}, err
        }

        return d, nil
}

実行

root@a67c959ef4b8:/work# go run main.go
=== json/base.json ===
{Name:Go Image:mcr.microsoft.com/devcontainers/go:1-1.22-bookworm Mounts:[{Type:bind Source:${localEnv:HOME}/.gitconfig Target:/home/vscode/.gitconfig}] Features:map[] RemoteUser:vscode}

=== json/additional.json ===
{Name: Image: Mounts:[{Type:bind Source:${localEnv:HOME}/.vim Target:/home/vscode/.vim}] Features:<nil> RemoteUser:}

=== Merged JSON json/base.json and json/additional.json ===
{Name:Go Image:mcr.microsoft.com/devcontainers/go:1-1.22-bookworm Mounts:[{Type:bind Source:${localEnv:HOME}/.gitconfig Target:/home/vscode/.gitconfig} {Type:bind Source:${localEnv:HOME}/.vim Target:/home/vscode/.vim}] Features:map[] RemoteUser:vscode}

参考資料

Go で JSON をマージする(Jeffail/gabs 編)

mikoto2000/devcontainer.vim: コンテナ上で Vim を使った開発をするためのツール。 VSCode Dev Container の Vim 版を目指しています。 の中で、 JSON をマージしたいという要求が出てきたので Jeffail/gabs: For parsing, creating and editing unknown or dynamic JSON in Go を試す。

開発環境起動

docker run -it --rm -v "$(pwd):/work" --workdir /work -v "$HOME/.vim:/root/.vim" golang:1.22.1-bookworm

環境構築

go mod init github.com/mikoto2000/golang/json/gabs/firststep
go get github.com/Jeffail/gabs/v2

JSON ファイル作成

json/base.json:

{
  "name":"Go",
  "image":"mcr.microsoft.com/devcontainers/go:1-1.22-bookworm",
  "mounts": [
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.gitconfig",
      "target": "/home/vscode/.gitconfig"
    }
  ],
  "features":{},
  "remoteUser":"vscode"
}

json/additional.json:

{
  "mounts": [
    {
      "type": "bind",
      "source": "${localEnv:HOME}/.vim",
      "target": "/home/vscode/.vim"
    }
  ]
}

プログラム実装

package main

import (
        "fmt"

        "github.com/Jeffail/gabs/v2"
)

const baseJsonFilePath = "json/base.json"
const additionalJsonFilePath = "json/additional.json"

func main() {
        parsedBaseJson, err := gabs.ParseJSONFile(baseJsonFilePath)
        if err != nil {
                panic(err)
        }
        fmt.Printf("=== %s ===\n", baseJsonFilePath)
        fmt.Println(parsedBaseJson.StringIndent("", "  "))
        fmt.Println()

        parsedAdditionalJson, err := gabs.ParseJSONFile(additionalJsonFilePath)
        if err != nil {
                panic(err)
        }
        fmt.Printf("=== %s ===\n", additionalJsonFilePath)
        fmt.Println(parsedAdditionalJson.StringIndent("", "  "))
        fmt.Println()

        parsedBaseJson.Merge(parsedAdditionalJson)
        fmt.Printf("=== Merged JSON %s and %s ===\n", baseJsonFilePath, additionalJsonFilePath)
        fmt.Println(parsedBaseJson.StringIndent("", "  "))
        fmt.Println()
}

実行

root@696abd662a3a:/work# go run main.go
=== json/base.json ===
{
  "features": {},
  "image": "mcr.microsoft.com/devcontainers/go:1-1.22-bookworm",
  "mounts": [
    {
      "source": "${localEnv:HOME}/.gitconfig",
      "target": "/home/vscode/.gitconfig",
      "type": "bind"
    }
  ],
  "name": "Go",
  "remoteUser": "vscode"
}

=== json/additional.json ===
{
  "mounts": [
    {
      "source": "${localEnv:HOME}/.vim",
      "target": "/home/vscode/.vim",
      "type": "bind"
    }
  ]
}

=== Merged JSON json/base.json and json/additional.json ===
{
  "features": {},
  "image": "mcr.microsoft.com/devcontainers/go:1-1.22-bookworm",
  "mounts": [
    {
      "source": "${localEnv:HOME}/.gitconfig",
      "target": "/home/vscode/.gitconfig",
      "type": "bind"
    },
    {
      "source": "${localEnv:HOME}/.vim",
      "target": "/home/vscode/.vim",
      "type": "bind"
    }
  ],
  "name": "Go",
  "remoteUser": "vscode"
}

参考資料

2024年3月29日金曜日

ttyd の main 関数を Rust プログラムから呼び出す(Rust から C の関数を呼び出す)

開発環境の準備

開発用コンテナの起動

docker run -it --rm --workdir /work -p 0.0.0.0:7681:7681 rust:1.77.0-slim-bookworm bash
WORKDIR=/work

ttyd のソースコード取得

git インストール。

apt update
apt install -y git

ttyd のリポジトリを clone.

cd $WORKDIR
git clone --depth 1 -b 1.7.5 https://github.com/tsl0922/ttyd

プロジェクトディレクトリへ移動。

cd $WORKDIR/ttyd

ttyd をライブラリとしてビルド

ttyd の README を参照しながら、ビルドに必要なパッケージをインストールする。

apt install -y build-essential cmake git libjson-c-dev libwebsockets-dev

前回の apt install と重複があるが、気にせず ttyd の README からそのままコピペ。

CMakeLists.txt を修正

実行バイナリではなく、 .a を生成するように CMakeLists.txt を修正する。

sed -i -e 's/add_executable(${PROJECT_NAME} ${SOURCE_FILES})/add_library(${PROJECT_NAME} STATIC ${SOURCE_FILES})/' ./CMakeLists.txt
  • add_executable: 実行バイナリを生成する
  • add_library: .a を生成する

関数 main の名前変更

別プログラムに組み込みたいので、エントリーポイントである main が存在すると困る。

なので、 mainttyd_main へリネームする。

sed -i -e 's/main(/ttyd_main(/' ./src/server.c

別プログラムから、 ttyd_main(2, ["ttyd", "-W", "bash"]) のような感じで呼び出すイメージ。

ttyd のビルド

mkdir build && cd build
cmake ..
make

これで、 libttyd.a が生成される。

ttyd を組み込む Rust プログラムを作る

ttydwrapper というプロジェクトを作って、 固定値で ttyd -W -t enableSixel=true bash コマンドを実行するのと同じように ttyd を呼び出すプログラムを作る。

bindgen に必要なパッケージのインストール

C 言語との FFI をするにあたり、 bindgen というコマンドを利用するので、それに必要なパッケージをインストールする。

apt update
apt install -y libclang-16-dev

Cargo プロジェクトの作成

今回は、 ttydwrapper という名前で作成。

cd $WORKDIR
cargo new ttydwrapper
cd ttydwrapper
rustup component add rustfmt

FFI 用のヘッダーファイルを作成

Rust から C の ttyd_main を呼び出すので、 ttyd_main の定義が記載されているヘッダーファイルを作成する。

Rust では、一般的に、 binding.h に必要なヘッダーファイルを列挙するらしい。

なので今回は、 ttyd.httyd_main を定義し、そのヘッダーファイルを binding.h へ記載する構成でヘッダーを作成した。

mkdir include
cat << EOF > ./include/ttyd.h
void ttyd_main(int argc, char **argv);
EOF

cat << EOF > ./include/binding.h
#include "ttyd.h"
EOF

バインディングを生成

bindgen コマンドで、ヘッダーから Rust のコードを生成する。

cargo install bindgen-cli
bindgen ./include/binding.h > ./src/bindings.rs

以下のような、 binding.rs が生成される。

/* automatically generated by rust-bindgen 0.69.4 */

extern "C" {
    pub fn ttyd_main(argc: ::std::os::raw::c_int, argv: *mut *mut ::std::os::raw::c_char);
}

生成された binding.rslib.rs からインクルードさせるのが一般的らしいので libs.rs を作る。

cat << EOF > ./src/lib.rs
include!("bindings.rs");
EOF

ライブラリをコピー

ライブラリは lib 以下に無いとダメなので .a を移動する。

mkdir $WORKDIR/ttydwrapper/lib
cp $WORKDIR/ttyd/build/libttyd.a $WORKDIR/ttydwrapper/lib/

ライブラリの情報を作成

build.rs にビルド時に必要なライブラリ情報の列挙をする。

cat << EOF > ./build.rs
fn main() {
    println!("cargo:rustc-link-search=native=$WORKDIR/ttydwrapper/lib");
    println!("cargo:rustc-link-search=native=/usr/lib/x86_64-linux-gnu");
    println!("cargo:rustc-link-lib=static=ttyd");
    println!("cargo:rustc-link-lib=dylib=uv");
    println!("cargo:rustc-link-lib=dylib=websockets");
    println!("cargo:rustc-link-lib=static=ssl");
    println!("cargo:rustc-link-lib=static=crypto");
    println!("cargo:rustc-link-lib=static=z");
    println!("cargo:rustc-link-lib=static=json-c");
}
EOF

ビルド時に build.rs の設定を使用するように修正

Cargo.toml[package] の末尾に build = "build.rs" を追加する。

(sed で実現できなかったので vim で編集した)

main 関数の作成

Rust の main 関数を作成し、 ttyd_main を呼び出すプログラムを作る。

cat << EOF > ./src/main.rs
use std::ffi::CString;
use std::os::raw::c_char;
use ttydwrapper::ttyd_main;

fn main() {

    let args = vec!["tty", "-W", "-t", "enableSixel=true", "bash"];

    let c_args: Vec<CString> = args.into_iter()
        .map(|arg| CString::new(arg).expect("CString::new failed"))
        .collect();

    let mut argv: Vec<*mut c_char> = c_args.iter()
        .map(|arg| arg.as_ptr() as *mut c_char)
        .collect();

    let argv_ptr: *mut *mut c_char = argv.as_mut_ptr();

    unsafe {
        ttyd_main(5, argv_ptr);
    }
}
EOF

ビルド

cargo build

これで ./target/debug/ttydwrapper が生成される。

動作確認

コンテナ起動時に 7681 をポートフォワーディングしているので、 ttydwrapper を実行した後 http://localhost:7681 へアクセスすると ttyd のターミナルが開く。

以上。

ここまで書いたところで、本当にやりたいのは Windows でだった事に気付いた…。

参考資料

2024年3月23日土曜日

Windows コンテナで Node.js アプリケーションをシングルバイナリにする

今回は、 Node.js アプリケーションのシングルバイナリ化をやってみる。

対象のアプリケーションは devcontainers/cli

nexe/nexeNode.js v21 の Single executable applications の 2 パターンでやってみる。

前提

  • OS: Windows 11 Pro 23H2 ビルド 22631.3155
  • Docker Desktop: Version 4.28.0 (139021)
  • 使用イメージ: mcr.microsoft.com/windows/servercore:ltsc2022

コンテナ起動

Node.js のビルドにはたくさんメモリが必要なので、 -m で明示的に指定。

docker run -it --rm --cpus 20 -m 16G -v "$(pwd):C:\dist" mcr.microsoft.com/windows/servercore:ltsc2022 powershell.exe

ビルド環境構築

開発ツール一式をインストール

Set-ExecutionPolicy Unrestricted -Force
iex ((New-Object System.Net.WebClient).DownloadString('https://boxstarter.org/bootstrapper.ps1'))
get-boxstarter -Force
Install-BoxstarterPackage https://raw.githubusercontent.com/nodejs/node/HEAD/tools/bootstrap/windows_boxstarter -DisableReboots
refreshenv

Visual Studio に必要なコンポーネントを追加

& 'C:\Program Files (x86)\Microsoft Visual Studio\Installer\setup.exe' modify `
    --installPath 'C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools' `
    --add Microsoft.VisualStudio.Component.Windows11SDK.22621 `
    --add Microsoft.VisualStudio.Component.VC.Tools.x86.x64 `
    --add Microsoft.VisualStudio.Component.VC.Runtimes.x86.x64.Spectre `
    --quiet

Node.js のインストール

choco install nodejs

パス環境変数の更新

$env:Path = [System.Environment]::GetEnvironmentVariable("Path","Machine") + ";" + [System.Environment]::GetEnvironmentVariable("Path","User")

devcontainers/cli をシングルバイナリにしてみる

nexe/nexe で作る

git clone --depth 1 -b v0.58.0 https://github.com/devcontainers/cli devcontainers-cli
cd devcontainers-cli
npm i -g yarn
yarn
yarn compile-prod
npx nexe -i ./devcontainer.js -t windows-x64-21.7.1 -b -o /dist/devcontainer-windows-x64-21.7.1-nexe.exe

~\.nexe\21.7.1\out\Release\node.exe に nexe 向けの node.exe が配置される。 これを ~\.nexe\windows-x64-21.7.1 に移動し、 -b を外すことで、次回以降、このバイナリを利用してシングルバイナリを作るようになる。

mv ~\.nexe\21.7.1\out\Release\node.exe ~\.nexe\windows-x64-21.7.1

Node.js の Single executable applications で作る

# シングルバイナリに使うための node 実行ファイルをコピー
Copy-Item 'C:\Program Files\nodejs\node.exe' .\devcontainer-windows-x64-21.7.1-sea.exe

# sea 用の設定ファイル作成
Write-Output '{"main": "./dist/spec-node/devContainersSpecCLI.js", "output": "sea-prep.blob"}' | Out-File -Encoding ascii .\sea-config.json

# blob の作成
node --experimental-sea-config sea-config.json

# BLOB を注入し、シングルバイナリを作る
npx postject devcontainer-windows-x64-21.7.1-sea.exe NODE_SEA_BLOB sea-prep.blob `
    --sentinel-fuse NODE_SEA_FUSE_fce680ab2cc467b6e072b8b5df1996b2

# コンテナの外にコピー
Copy-Item .\devcontainer-windows-x64-21.7.1-sea.exe \dist

参考資料

変更履歴

日付 内容
2024/3/23 新規作成
2024/4/2 nexe 用 node バイナリ格納場所の間違いを修正(~/nexe -> ~/.nexe)

2024年3月22日金曜日

Vim に Ex コマンドを追加してビルドする

追加するために修正が必要な箇所を把握するのが目的なので、 echo をそのまま呼び出す myecho コマンドを追加する。

前提

  • OS: Windows 11 Pro 23H2 ビルド 22631.3155
  • Docker Desktop: Version 4.28.0 (139021)
  • 使用イメージ: debian:bookworm-slim
  • ビルドする Vim のバージョン: v9.1.0196

ドキュメントの追加・更新はスコープ外とする。

コンテナ起動

docker run -it --rm debian:bookworm-slim

Vim をビルドできる環境を整える

必要なパッケージのインストール

apt update
apt install -y build-essential libncurses-dev vim git

※ Vim は、 Ex コマンド追加後のコマンドのインデックス作り直し処理で必要になる

作業用ディレクトリ作成

mkdir ~/work
cd ~/work

Vim のソースコード入手

v9.1.0196 の最新のコミットのみをダウンロード。

git clone -b v9.1.0196 --depth 1 https://github.com/vim/vim.git
cd vim

ビルド・実行

./configure
make -j16
src/vim

vim が起動した。 OK.

Ex コマンドの追加

Ex コマンドの実装ファイルを作成

引数をそのまま既存関数 ex_echo へ丸投げする ex_myecho 関数を作る。

src/myecho.c:

#include "vim.h"

void
ex_myecho(exarg_T *eap)
{
  ex_echo(eap);
}

src/proto/myecho.pro:

/* myecho.c */
void ex_myecho(exarg_T *eap);
/* vim: set ft=c : */

作成した Ex コマンドを Vim に組み込む

src/proto.h へ追加

19 行目から 123 行目までの # include の並びに、 回作成した myecho.pro を追加する。

# include "myecho.pro"

src/ex_cmds.h へ Ex コマンドを追加

117 行目からの EXCMD マクロの並びに、今回追加するコマンドを追加する。

今回は末尾に追加した。

EXCMD(CMD_myecho,   "myecho",       ex_myecho,
    EX_EXTRA|EX_NOTRLCOM|EX_EXPR_ARG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
    ADDR_NONE),

フラグの意味は、このヘッダーファイルの先頭に記載されている。

今回は、 echo のモノをそのままコピペ。

Ex コマンドインデックスの更新

src ディレクトリへ移動し、 make cmdidxs コマンドを実行する。

以下のように、 Ex コマンドのインデックスが更新される。

diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h
index 658f05e..26031d8 100644
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -53,7 +53,7 @@ static const unsigned char cmdidxs2[26][26] =
   /* j */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 },
   /* k */ {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* l */ {  3, 11, 15, 19, 20, 25, 28, 33,  0,  0,  0, 35, 38, 41, 45, 51,  0, 53, 62, 54, 55, 59, 61,  0,  0,  0 },
-  /* m */ {  1,  0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 16 },
+  /* m */ {  1,  0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,288, 16 },
   /* n */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  8, 10,  0,  0,  0,  0,  0, 17,  0,  0,  0,  0,  0 },
   /* o */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  5,  0,  0,  0,  0,  0,  0,  9,  0, 11,  0,  0,  0 },
   /* p */ {  1,  0,  3,  0,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  7,  9,  0,  0, 16, 17, 26,  0, 28,  0, 29,  0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] =
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };

-static const int command_count = 594;
+static const int command_count = 595;

ビルドスクリプトの更新

以下ビルドスクリプトに、 myecho.cmyecho.o(または myecho.obj) を追加する。

move. で検索かけて、ヤンク・ペースト、そのご myhello. に書き換える感じ。

  • src/Make_ami.mak
  • src/Make_cyg_ming.mak
  • src/Make_mvc.mak
  • src/Make_vms.mms
  • src/Makefile

ビルド

make distclean
./configure
make -j16

これで、 src/vim に出力されたバイナリを実行し、 :myecho "abcd" とすると、 abcd がエコーされる。

以上。

最終的な差分

差分.patch
diff --git a/src/Make_ami.mak b/src/Make_ami.mak
index 9e9ebe3..d99501a 100644
--- a/src/Make_ami.mak
+++ b/src/Make_ami.mak
@@ -138,6 +138,7 @@ SRC += \
        misc2.c \
        mouse.c \
        move.c \
+       myecho.c \
        normal.c \
        ops.c \
        option.c \
diff --git a/src/Make_cyg_ming.mak b/src/Make_cyg_ming.mak
index 7afb6e0..b64eb93 100644
--- a/src/Make_cyg_ming.mak
+++ b/src/Make_cyg_ming.mak
@@ -821,6 +821,7 @@ OBJ = \
        $(OUTDIR)/misc2.o \
        $(OUTDIR)/mouse.o \
        $(OUTDIR)/move.o \
+       $(OUTDIR)/myecho.o \
        $(OUTDIR)/mbyte.o \
        $(OUTDIR)/normal.o \
        $(OUTDIR)/ops.o \
diff --git a/src/Make_mvc.mak b/src/Make_mvc.mak
index 4db2298..0aacd2b 100644
--- a/src/Make_mvc.mak
+++ b/src/Make_mvc.mak
@@ -698,6 +698,7 @@ OBJ = \
        $(OUTDIR)\misc2.obj \
        $(OUTDIR)\mouse.obj \
        $(OUTDIR)\move.obj \
+       $(OUTDIR)\myecho.obj \
        $(OUTDIR)\normal.obj \
        $(OUTDIR)\ops.obj \
        $(OUTDIR)\option.obj \
@@ -1622,6 +1623,8 @@ $(OUTDIR)/mouse.obj:      $(OUTDIR) mouse.c  $(INCL)

 $(OUTDIR)/move.obj:    $(OUTDIR) move.c  $(INCL)

+$(OUTDIR)/myecho.obj:  $(OUTDIR) myecho.c  $(INCL)
+
 $(OUTDIR)/mbyte.obj:   $(OUTDIR) mbyte.c  $(INCL)

 $(OUTDIR)/netbeans.obj:        $(OUTDIR) netbeans.c $(NBDEBUG_SRC) $(INCL) version.h
@@ -1889,6 +1892,7 @@ proto.h: \
        proto/misc2.pro \
        proto/mouse.pro \
        proto/move.pro \
+       proto/myecho.pro \
        proto/mbyte.pro \
        proto/normal.pro \
        proto/ops.pro \
diff --git a/src/Make_vms.mms b/src/Make_vms.mms
index f050c9d..b15d7b0 100644
--- a/src/Make_vms.mms
+++ b/src/Make_vms.mms
@@ -396,6 +396,7 @@ SRC = \
        misc2.c \
        mouse.c \
        move.c \
+       myecho.c \
        normal.c \
        ops.c \
        option.c \
@@ -528,6 +529,7 @@ OBJ = \
        misc2.obj \
        mouse.obj \
        move.obj \
+       myecho.obj \
        normal.obj \
        ops.obj \
        option.obj \
@@ -1033,6 +1035,9 @@ mouse.obj : mouse.c vim.h [.auto]config.h feature.h os_unix.h   \
 move.obj : move.c vim.h [.auto]config.h feature.h os_unix.h   \
  ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
+myecho.obj : myecho.c vim.h [.auto]config.h feature.h os_unix.h   \
+ ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
+ [.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
 mbyte.obj : mbyte.c vim.h [.auto]config.h feature.h os_unix.h   \
  ascii.h keymap.h termdefs.h macros.h structs.h regexp.h gui.h beval.h \
  [.proto]gui_beval.pro option.h ex_cmds.h proto.h errors.h globals.h
diff --git a/src/Makefile b/src/Makefile
index 33903d3..18a714e 100644
--- a/src/Makefile
+++ b/src/Makefile
@@ -1546,6 +1546,7 @@ BASIC_SRC = \
        misc2.c \
        mouse.c \
        move.c \
+       myecho.c \
        normal.c \
        ops.c \
        option.c \
@@ -1704,6 +1705,7 @@ OBJ_COMMON = \
        objects/misc2.o \
        objects/mouse.o \
        objects/move.o \
+       objects/myecho.o \
        objects/normal.o \
        objects/ops.o \
        objects/option.o \
@@ -1893,6 +1895,7 @@ PRO_AUTO = \
        misc2.pro \
        mouse.pro \
        move.pro \
+       myecho.pro \
        netbeans.pro \
        normal.pro \
        ops.pro \
@@ -3374,6 +3377,9 @@ objects/mouse.o: mouse.c
 objects/move.o: move.c
        $(CCC) -o $@ move.c

+objects/myecho.o: myecho.c
+       $(CCC) -o $@ myecho.c
+
 objects/mbyte.o: mbyte.c
        $(CCC) -o $@ mbyte.c

@@ -3999,6 +4005,11 @@ objects/move.o: move.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
  libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
  globals.h errors.h
+objects/myecho.o: myecho.c vim.h protodef.h auto/config.h feature.h os_unix.h \
+ auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
+ proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
+ libvterm/include/vterm_keycodes.h alloc.h ex_cmds.h spell.h proto.h \
+ globals.h errors.h
 objects/normal.o: normal.c vim.h protodef.h auto/config.h feature.h os_unix.h \
  auto/osdef.h ascii.h keymap.h termdefs.h macros.h option.h beval.h \
  proto/gui_beval.pro structs.h regexp.h gui.h libvterm/include/vterm.h \
diff --git a/src/ex_cmdidxs.h b/src/ex_cmdidxs.h
index 658f05e..26031d8 100644
--- a/src/ex_cmdidxs.h
+++ b/src/ex_cmdidxs.h
@@ -53,7 +53,7 @@ static const unsigned char cmdidxs2[26][26] =
   /* j */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  0,  0,  0,  0,  0 },
   /* k */ {  0,  0,  0,  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 },
   /* l */ {  3, 11, 15, 19, 20, 25, 28, 33,  0,  0,  0, 35, 38, 41, 45, 51,  0, 53, 62, 54, 55, 59, 61,  0,  0,  0 },
-  /* m */ {  1,  0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0, 16 },
+  /* m */ {  1,  0,  0,  0,  7,  0,  0,  0,  0,  0, 10,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,288, 16 },
   /* n */ {  0,  1,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  5,  8, 10,  0,  0,  0,  0,  0, 17,  0,  0,  0,  0,  0 },
   /* o */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  1,  2,  5,  0,  0,  0,  0,  0,  0,  9,  0, 11,  0,  0,  0 },
   /* p */ {  1,  0,  3,  0,  4,  0,  0,  0,  0,  0,  0,  0,  0,  0,  7,  9,  0,  0, 16, 17, 26,  0, 28,  0, 29,  0 },
@@ -69,4 +69,4 @@ static const unsigned char cmdidxs2[26][26] =
   /* z */ {  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0,  0 }
 };

-static const int command_count = 594;
+static const int command_count = 595;
diff --git a/src/ex_cmds.h b/src/ex_cmds.h
index bd26e81..0f9c1dc 100644
--- a/src/ex_cmds.h
+++ b/src/ex_cmds.h
@@ -1904,6 +1904,10 @@ EXCMD(CMD_decrement,     "--",           ex_incdec,
        EX_EXTRA|EX_TRLBAR|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
        ADDR_NONE),

+EXCMD(CMD_myecho,              "myecho",               ex_myecho,
+       EX_EXTRA|EX_NOTRLCOM|EX_EXPR_ARG|EX_SBOXOK|EX_CMDWIN|EX_LOCK_OK,
+       ADDR_NONE),
+
 #undef EXCMD

 #ifndef DO_DECLARE_EXCMD
diff --git a/src/proto.h b/src/proto.h
index 50802ce..656e210 100644
--- a/src/proto.h
+++ b/src/proto.h
@@ -121,6 +121,7 @@ extern int _stricoll(char *a, char *b);
 # ifdef FEAT_VIMINFO
 #  include "viminfo.pro"
 # endif
+# include "myecho.pro"

 // These prototypes cannot be produced automatically.
 int smsg(const char *, ...) ATTRIBUTE_COLD ATTRIBUTE_FORMAT_PRINTF(1, 2);

参考資料