Quantcast
Channel: Vimタグが付けられた新着記事 - Qiita
Viewing all 5722 articles
Browse latest View live

viの使い方を覚え始めた

$
0
0

はじめに

今まではWindows関連の仕事が多かったので、viを使う機会は殆どありませんでした。そのため、最低限のコマンドだけしか覚えずに、10年以上過ごしてきました。
最近になってLinuxサーバの構築の機会が増えたので、ようやくviのコマンドを覚え始めました。

最低限覚えていたコマンド

Windowsの場合、viでなければダメな場面はほとんどなかったので、viを覚えるメリットがありませんでした。それでも、ESC, i, :wq, :!q この4つのコマンドだけは忘れないようにしてました。これだけ覚えていれば、滅多に無いviしか使えないときでも、編集と保存ができるので何とか凌ぐことができました。

操作結果
ECS入力モード -> コマンドモード
iカーソルの位置に文字を入力
:wq保存して終了
:!q保存してせず終了

最近覚え出したコマンド

1行の編集

httpd.confなどの設定ファイルは1行単位で記述されていることが多いので、まずこれらのコマンドを覚えました。削除の方がよく使います。

操作結果
yy1行のコピー
pペースト
dd1行の削除

検索

設定ファイルに間違いがないときは使うことはなかったのです。でも、サーバが思ったとおり動かず、原因を調べるために設定ファイルを検索したくなることが増えたので、使い出しました。

操作結果
/キーワードキーワードを検索
n次の検索

最終行へ移動

ログファイルを確認するときに、最終行へ移動して確認するときに使いました。

操作結果
G最終行への移動

おわりに

今後も使う場面に応じて、少しずつ覚えていきます。


vimオペレータの自作(手習い)

$
0
0

概要

Vim のオペレータを自作してみる事に関する、ごく初歩的な試みです。

とは言え、特にオペレータを作ってやりたいことがある、と言うわけではありません。

ただ、ちょっとした手順を何度か繰り返したい時に、qのレコーディングであれこれするよりも、気軽に中身を書き換えて使いまわせる自前のオペレータのスケルトン的な物があると便利かもなー、と思って書き始めたものです。

実際のところは、Vim ヘルプを見ていて、やってみたくなったというのが一番の理由です。

世の中にはいろいろと便利なオペレータを追加するプラグインもありますし、そもそも自作オペレータの追加を容易にするプラグインというのもあるらしいので、普通はそういうものを使うのだと思います。
・・・、あえて自分で作ってみるという事に意義があると思うのです。

車輪の再発明ではありません。車輪がすでに発明はされていることは知っています。

既製品の車輪が手に入らない環境に住まうことを強いられている(社内ルールと言います)ため、拙い車輪でも手作りしてみるというわけです。

基本的には舶来品は全てご禁制(ネットという大海を経由するだけでアウトなので、国産どころか自家製だろうが当然 NG)なのです。

お上(情報システム部)公認のもの以外は存在を許されません。そもそもエディタ選択の自由もない。もはや宗教弾圧である。・・・だのに役員共は生産性を上げろという。あと 7zip 禁止して laplus 強要ってどういうつもりなのか?・・・いや愚痴はやめよう。

Vim が Linux に最初から入っててくれて本当に良かった。その理由を挙げなかったら、GVim のダウンロード申請も通らなかっただろうし。っていうかあの人たち vi/Vim の名前すら知らなかったし。本当に勘弁して頂きたかった。

・・・というわけで、その場で書くだけで簡単に機能追加・変更ができる使えるスクリプトがあると、本当に本当の本当にとっても助かるのです。

愚痴じゃないですよ?幸運に感謝してるんですよ。いや本当に。

環境

GVIM 8.2.1287 (kaoriya 2020/07/24)

オペレータって?

オペレータとは、通常モードから、オペレータ待機モードに入る時に押されるコマンドです。
cとかdとかyとか・・・いろいろあります。

詳しくは:h operator参照なのです。

オペレータが受け取るもの(モーション)

オペレータ待機モードでは、続けてモーションを受け取ります。モーションというのはカーソル移動や、テキストオブジェクトの事です。

これまた:h motion.txt:h movementを参照するのが一番です。

カーソル移動

オペレータに続けてカーソル移動コマンドを行なうと、そのコマンドでカーソルが動く前(A 地点)と、動いた後(B 地点)の、 A 地点~ B 地点の間の文字列を対象に、オペレータが何かを行います。

  • オペレータdに対して、dwなら、デリート、ワード。単語削除。
  • オペレータcに対して、c2wで、チェンジ、ツー、ワード。2文字置き換え。

・・・みたいな。

とはいえ、yjのように行を移動するカーソル移動を行うと、行単位が対象になります。
また、該当行のみを対象にしたい場合は、オペレータを続けて打つという vi 互換のコマンドがありますね。ccとかddとかyyとか。

:h left-right-motions:h word-motionsや、:h object-motionで動ける範囲だと、文字列を対象。

:h up-down-motionsだと、行単位が対象になるような気がします。

テキストオブジェクト

オペレータ待機モード中に、テキストオブジェクトを表わすコマンドを入力することで、テキストオブジェクトが示す範囲を対象に、オペレータが処理を行います。
ちなみにテキストオブジェクトは、オペレータ待機モードだけではなく、ビジュアルモードでもその威力を発揮します。

詳しくは:h objects:h text-objectsを参照なのです。

………しかし、書けば書くほど、下手なヘルプの引用にしかならないという自体に直面し、ただただ Vim ヘルプの素晴らしさに圧倒されるばかりですね。

へこたれずに、多少は自分なりに書き続けてみます。

とは言え、『テキストオブジェクトにはこれこれこういうものがあります』と言う様な事を、記事内でわざわざ一覧にしたりはしません。それこそヘルプ嫁というやつです。

テキストオブジェクトは2文字で表されます。
うまく言えませんが、1文字目が範囲、2文字目が対象、・・・という感じです。

「ドコ」の「ナニ」という順番です。「中、家の」「周り、机の」みたいな。

1文字目の範囲について、自分は、i(inner :内側)、a(around:周り)と覚えています。
ヘルプを読んでみると、どうやらaは"a"(不定冠詞) らしいのですが? 耳馴染みがないので、アラウンドと独習してしまっていました。
・・・意味的には別に間違ってはいないはず。(むしろ「周り」の方が意味が通じるような気がする)

2文字目の対象については、wが単語だったりするのでカーソル移動と似てるのかな、と最初は思ってしまいがちですが(そんなことない?)、実際には全然違います。

bは後方の単語などではなく、ブロックを意味しますし、lは1文字右などではなく、ライン(1行)です。
hjk等は意味を持ちません。

実際に使う時は、例えば、ciwで「単語の内側を置換(消して、入力モードに遷移)」という意味になります。

オペレータcに対して、テキストオブジェクトを用いてciwとする場合と、モーションを用いて同じことをしたい場合には、bを前置してbcwとすれば同じ結果を得ることができます。

ただし、ciwbcwの二者では、.で繰り返したときに違いが出てきます。

ciwはこの3文字のキーストローク自身が一つのオペレータの操作です。つまり.で繰り返される単位になるのです。

bcwは、実際にはb(カーソル移動)とcw(置換)の2つの操作に分割されています。なので、次の単語中にカーソルがある状態で.を押下しても、再度実行されるのはcwだけであり、期待した結果が得られない可能性があります。

具体的に言うと単語の先頭にカーソルがあるときにしかciwと同じ結果は得られません。
b.とする必要があると言ったほうが分かりやすいでしょうか。

若干話が脱線しますが、もう少し実例寄りだと、ドキュメントにテーブル名やカラム名が全部大文字で書かれていて、それを切り貼りしながら SQL 文を作成していたら、むしろ予約語が小文字で、非予約語が大文字という有様になる事とかあるじゃないですか。ありませんか?

普段は別に気にせずそのまま使いますが、このクエリはチョイチョイ使いそうなので残しておきたいな、とか思う時には、ちょっと見た目にも気を遣うじゃないですか。

そういう時に、g~iwとかやっておいて、wbで移動しながら.すると、SQL 文の大文字小文字を単語単位で整えるのが楽ちんです。
jkで行を移動したときに、カーソル位置が単語の途中に乗ってしまった時にも、気にせず.することができます。

閑話休題。

まぁ、もっと簡単にテキストオブジェクトの優れている点を挙げろ、と言われればば、i"i'i<等といった、括り文字の内側、という反則的な便利さ等があるでしょう。

上記の例などから、テキストオブジェクトの方がカーソル移動よりも優れている、と一概に言えるかというと、単純にそういうワケでもありません。
オペレータによっては、モーションでカーソル位置が移動する、という作用まで織り込んで.で繰り返すことができるためです。
また、カーソルの進む方向にだけオペレータの作用を留めたい場合は、あえてカーソル移動を選ぶことがあります。

gqjなどが好例です。・・・というかそれくらいしか思いつかないし、gqapという使い方もあるので、カーソル移動だからとかテキストオブジェクトだからどうだ、と言う話とは関係ないのですが。

だって、自前で字下げや改行の微修正とかしてる場合に、段落単位のgqapだと、整形し直して欲しくないところまで整形してくれちゃうからヤなんですよ。あとgqj...の方がタイプが楽だし。

再び話が脱線していきますけど、プロポーショナルフォント表示が初期設定になってるメーラーが多くて、等幅フォント的に整形したつもりではガッタガタの意味の分からない改行が散りばめられた怪文になるので、何も考えずgq任せに整形するというわけにはいかないんですよね。

再び閑話休題。

当然、まだ私が知らないだけで、gq以外にも、カーソル移動の方が捗るぜ!というオペレータがあるかもしれません。

モーションを受け取った後に、オペレータが行うこと

それはもう、オペレータによって千差万別、というか、オペレータの数だけ、行われることはあります。

もちろん実際には千も万もありません。並みのエディタよりは多い事は確かですが・・・いやむしろ「テキストに対する作用」の種類としては少ないのかも?
オペレータとモーションの掛け合わせによって、結果として起すことができる事象のバリエーションが凄まじい事になっている。というのが正しいですね。
カーソル移動の [COUNT] まで込みで考えたら、余裕で万越えるどころか、もはや無限といってもいいのかも。素晴らしい・・・。

・・・ともあれ、多様なオペレータを含む数多くのコマンドが用意されているので、vi のノーマルモードってほとんど全ての1文字アルファベットに、大文字と小文字を区別して、何らかの有意なコマンドが割り振り済みなんですよね。

なので、「自分のやってほしいコト」一つずつのために、空いているアルファベットを捻り出すことは容易ではありません。むしろデフォルトの機能を殺すことを覚悟で載せていく必要があります。

「コマンドなんちゃらと同じ。推奨されない。」とか書かれているようなコマンドなら遠慮なく潰せますが、素の状態の Vim をこよなく愛してしまっている身としては、「ピュアな Vim をあまり穢したくない」という訳の分からないジレンマを抱えることになります。(kaoriya 版という時点で既にピュアではないのですが、それは言わないお約束)

後は、<Leader><LocalLeader>に押し込むとか。修飾キーと同時押しにするとか。n打鍵にするとか。
他には、これはもう本当に最終手段ですが、ファンクションキーに乗せるか・・・、という苦渋の決断を迫られます。

また、他のプラグインを入れたりしている場合は、それらとの兼ね合いを考える必要もありそうですね。(私はプラグインを殆ど入れていないので関係ないのですが)

まぁ、そんなわけで、任意の1文字を潰して、その1文字のオペレータでやる内容は、その都度都度、自分なりに書き換えてしまおう。・・・というのが今回の記事の(やっと)目的でした。

というわけで、とりあえずZを潰して、なにやらやってみる、という物を、ペペッと書いてみました。

オペレータ「Z」実装サンプル

通常モードでZ<motion>または、ヴィジュアルモードでZとする事で、モーションまたは選択の範囲が何行目~何行目で、何という文字列が対象だったのかを echo 表示するだけのオペレータです。

実体はechorange()という関数です。

・・・どうしよう。
ほぼほぼ:h map-operatorのサンプルの丸パクリだから、解説するほどのことがまるでない。
いやマジでヘルプをご覧あれ。

動作を試してみる場合は、適当な名前に拡張子.vimをつけて保存して、Vim で開いてから:so %として自身を読み込ませてから使ってみてください。

ヴィジュアルモードは、文字選択、行選択、矩形選択に対応しています。

オペレータ自身への [count] には対応できていません。モーションへの [count] はうまくいくんですが。
オペレータとして今後の課題ですね。

opfunc_test.vim
nnoremap <silent> Z :setopfunc=<SID>echorange<CR>g@
vnoremap <silent> Z :<C-U>call<SID>echorange(visualmode(),1)<CR>fu!s:echorange(type,...)"状態退避&変更let sel_save =&selectionlet&selection="inclusive"let reg_save = @@

    "可変長引数あり(vmapから)ifa:0"範囲再選択silent exe "normal! gv""開始・終了行を取得let line1 =line('.')| normal!olet line2 =line('.')| normal!oletfirst= line1<line2? line1:line2
        letlast= line1<line2? line2:line1

        "選択範囲をヤンクsilent exe "normal! gvy""上記以外、g@(nmapから)else"モーションの開始・終了マークから、開始・終了行を取得letfirst=line("'[")letlast=line("']")"モーション範囲をヤンクifa:type=='line'silent exe "normal! '[V']y"elsesilent exe "normal! `[v`]y"endifendif"タイプ、および、開始・終了行を表示echona:typeechon': '.first.' - '.lastechon"\n""レジスタ内容を表示ifa:type=='char'&& a:type!=0echon' "'. @@ .'"'elseechon @@
    endif"状態復帰let&selection= sel_save
    let @@ = reg_save
endfu

コマンドでも同じことをやってみたい(オマケ)

せっかく作った処理なので、オペレータからだけではなく、コロンコマンドでも実行してみたいな、と思ったので、少し追記してみました。

コメントの「(★)」箇所が追加した部分です。

ユーザ定義コマンドの場合は、-countを付けると [count] 指定できるようになるらしいのですが、-range指定と同時に指定する場合はどのようにしたらいいのかまだわかっていません。

ザコが手探りしてる感満載の記事でしょう?

opfunc_test2.vim
"コマンド定義(★)com!-buffer-range Echorange call<SID>echorange('command',<line1>,<line2>) 

nnoremap <silent> Z :setopfunc=<SID>echorange<CR>g@
vnoremap <silent> Z :<C-U>call<SID>echorange(visualmode(),1)<CR>fu!s:echorange(type,...)"状態退避&変更let sel_save =&selectionlet&selection="inclusive"let reg_save = @@

    "コマンドから (★)ifa:type=='command'"開始・終了行を取得letfirst=a:1letlast=a:2"行範囲をヤンクsilent exe first.','.last.'y'"コマンド以外で、可変長引数あり(vmapから)elseifa:0"範囲再選択silent exe "normal! gv""開始・終了行を取得let line1 =line('.')| normal!olet line2 =line('.')| normal!oletfirst= line1<line2? line1:line2
        letlast= line1<line2? line2:line1

        "選択範囲をヤンクsilent exe "normal! gvy""上記以外、g@(nmapから)else"モーションの開始・終了マークから、開始・終了行を取得letfirst=line("'[")letlast=line("']")"モーション範囲をヤンクifa:type=='line'silent exe "normal! '[V']y"elsesilent exe "normal! `[v`]y"endifendif"タイプ、および、開始・終了行を表示echona:typeechon': '.first.' - '.lastechon"\n""レジスタ内容を表示ifa:type=='char'&& a:type!=0echon' "'. @@ .'"'elseechon @@
    endif"状態復帰let&selection= sel_save
    let @@ = reg_save
endfu

応用例

上記の例では、さすがに何の役にも立ちませんので、もう少し意味ありげな使い方を模索してみました。

・・・といっても、範囲をソートするだけで、:'<,'>!sortと変わらないため、相変わらず実用性は皆無です。

一応、テンプレとして echorange() は残しつつ、sortrange を追加して、共通部分を切り出しました。
今更ですが、可変引数については、:h a:0または、を見るとわかります。
:h ...でも同じですし、a:000でもヘルプ同じです。

後は、Leaderキーを使ったマップにしてあります。
:h <leader>:h <Leader>:h mapleaderを参照の事。

opfunc_test3.vim
"範囲を表示するだけcom!-buffer-range Echorange call<SID>echorange('command',<line1>,<line2>)
nnoremap <silent><LocalLeader>z :setopfunc=<SID>echorange<CR>g@
vnoremap <silent><LocalLeader>z :<C-U>call<SID>echorange(visualmode(),1)<CR>fu!s:echorange(type,...) "{{{calls:fuStart(a:type,a:000)"タイプ、および、開始・終了行を表示echona:typeechon': '.s:first.' - '.s:lastechon"\n""レジスタ内容を表示ifa:type=='char'&& a:type!=0echon' "'. @@ .'"'elseechon @@
    endifcalls:fuEnd()
endfu "}}}"範囲をソートするcom!-buffer-range Sortrange call<SID>sortrange('command',<line1>,<line2>)
nnoremap <silent><LocalLeader>s :setopfunc=<SID>sortrange<CR>g@
vnoremap <silent><LocalLeader>s :<C-U>call<SID>sortrange(visualmode(),1)<CR>fu!s:sortrange(type,...) "{{{calls:fuStart(a:type,a:000)"行単位以外は何もしないifa:type!='line'&& a:type!='V'&& a:type!='command'returnendif

    exe s:first.','.s:last.'!sort'"3"2"1calls:fuEnd()
endfu "}}}"--------------------------------------------------------------------------------"以降は共通処理{{{"--------------------------------------------------------------------------------fus:fuStart(type,pList) "{{{setlazyredraw"状態退避&変更lets:save_wi=winsaveview()lets:sel_save=&selectionlet&selection="inclusive"lets:reg_save= @@

    "コマンドからifa:type=='command'"開始・終了行を取得lets:first=a:pList[0]lets:last=a:pList[1]"行範囲をヤンクsilent exe s:first.','.s:last.'y'"コマンド以外で、可変長引数あり(vmapから)elseifa:type=='v'||a:type=='V'||a:type=="<c-v>""範囲再選択silent exe "normal! gv""開始・終了行を取得let line1 =line('.')| normal!olet line2 =line('.')| normal!olets:first= line1<line2? line1:line2
        lets:last= line1<line2? line2:line1

        "選択範囲をヤンクsilent exe "normal! gvy""上記以外、g@(nmapから)else"モーションの開始・終了マークから、開始・終了行を取得lets:first=line("'[")lets:last=line("']")"モーション範囲をヤンクifa:type=='line'silent exe "normal! '[V']y"elsesilent exe "normal! `[v`]y"endifendifendf "}}}fus:fuEnd() "{{{"状態復帰let&selection=s:sel_savelet @@ =s:reg_savecallwinrestview(s:save_wi)setnolazyredrawendf "}}}"}}}

最後に

任意の箇所で、任意のテキスト範囲に対して、正規表現使いたい放題で任意の処理を、任意のタイミングで発動できる、ってすごいことですよね。

任意のテキスト範囲(テキストオブジェクトですな)を追加するプラグインもあるので、そのうち手を出してみたいですねぇ。

vim-plug でRuby on Rails関連PluginをInstallしてみた

$
0
0

概要

普段、Ruby on Rails開発でVSCodeを使っているが、無性にVimが使いたくなったので
いくつかのVim Plugin Managerからvim-plugを選択、
pluginをインストールしてみた。

ローカル環境

  • macOS Catalina 10.15.7

インストール手順

vim-plug ダウンロード

$ curl -fLo ~/.vim/autoload/plug.vim --create-dirs \
    https://raw.githubusercontent.com/junegunn/vim-plug/master/plug.vim

.vimrc に以下Pluginを追記

ファイルツリー表示Pluginと rails関連Pluginを記述

# .vimrc ファイルツリー、rails関連プラグイン
call plug#begin()
Plug 'scrooloose/nerdtree', { 'on':  'NERDTreeToggle' }
Plug 'tpope/vim-rails'
Plug 'tpope/vim-endwise'
Plug 'junegunn/fzf', { 'do': { -> fzf#install() } }
Plug 'junegunn/fzf.vim'
call plug#end()

map <C-n> :NERDTreeToggle<CR> ←←ファイルツリーの表示・非表示を<Ctrl + n>にマッピング

vim起動し、.vimrc上のPluginをインストール

vim起動し、:PlugInstallを入力
image.png

初めてvimにプラグインを入れてみた

$
0
0

駆け出しエンジニア兼プロゲーマーのガリレオです。
ゴリラ先生を習いvimを使い始めて7ヶ月が経ちましたが、自分でvimのプラグインを入れたことが無かったので入れてみました。

内容としてほぼ(というか全部?)ゴリラ先生の記事と一緒になってしまいましたが・・・

プラグインを入れるのは思ったより簡単だったのでどんどんカスタマイズしていきたい。

参考記事

導入方法

vim ~/.vimrc

" dein.vim settings {{{
" install dir {{{
let s:dein_dir = expand('~/.cache/dein')
let s:dein_repo_dir = s:dein_dir . '/repos/github.com/Shougo/dein.vim'
" }}}

" dein installation check {{{
if &runtimepath !~# '/dein.vim'
  if !isdirectory(s:dein_repo_dir)
    execute '!git clone https://github.com/Shougo/dein.vim' s:dein_repo_dir
  endif
  execute 'set runtimepath^=' . s:dein_repo_dir
endif
" }}}

" begin settings {{{
if dein#load_state(s:dein_dir)
  call dein#begin(s:dein_dir)

  " .toml file
  let s:rc_dir = expand('~/.vim')
  if !isdirectory(s:rc_dir)
    call mkdir(s:rc_dir, 'p')
  endif
  let s:toml = s:rc_dir . '/dein.toml'

  " read toml and cache
  call dein#load_toml(s:toml, {'lazy': 0})

  " end settings
  call dein#end()
  call dein#save_state()
endif
" }}}

" plugin installation check {{{
if dein#check_install()
  call dein#install()
endif
" }}}

" plugin remove check {{{
let s:removed_plugins = dein#check_clean()
if len(s:removed_plugins) > 0
  call map(s:removed_plugins, "delete(v:val, 'rf')")
  call dein#recache_runtimepath()
endif
" }}}

  • プラグインの追加

今回はキャメルケースとスネークケースを変換するプラグインを導入します。
今まで手動でやっていたけど量が多いと面倒なのでプラグイン導入を決意。

vim ~/.vim/dein.toml

[[plugins]]
repo = 'tyru/operator-camelize.vim'

[[plugins]]
repo = 'kana/vim-operator-user'
  • プラグインの設定

vim ~/.vimrc

vmap <leader>c <plug>(operator-camelize)
vmap <leader>C <plug>(operator-decamelize)
  • vimの再起動
    :source ~/.vimrc

  • 動作確認

  1. ビジュアルモードで選択
  2. スペース+Cでスネークケースに変換
  3. スペース+cでキャメルケースに変換

output.gif

coc.nvimの多機能セレクタ(CocList)の検索をfzfにやらせるプラグイン

$
0
0

序文と紹介

以前 この記事coc.nvimについて紹介させていただきました。

coc.nvimの機能の1つに CocListというものがあります。

CocListの機能についてはこちらextentionのトグル、diagnosticsのリスト表示とジャンプ、symbolの検索などを呼び出すメニュー機能です

ただし、CocListの候補検索部分が少し重いようで、検索先の候補が多いと重くなっていました。
これをGo製のあいまい検索ツール fzfにやらせることで軽快にするNeoVimプラグインを見つけましたので紹介させていただこうと思います。

セットアップ

インストール

ここにVimプラグインがあるのでそれぞれのプラグインマネージャでインストールしてください。

vim-plugを導入済みの場合

ぼくは vim-plugなので、以下をinit.vimに書き加え :PlugInstallします。

init.vim
Plug 'antoinemadec/coc-fzf'

すると :CocFzfListというコマンドが追加されるので設定ファイルにキーバインドなどを設定します。

オプション:Escキーで抜ける設定

デフォルトだとEscキーで抜けられないので以下のキーバインドの設定を加えます。

init.vim
tnoremap<expr><Esc>(&filetype=="fzf") ? "<Esc>":"<c-\><c-n>"

オプション:Floating Windowでの表示の設定

そのままでも使えますが、Floating Windowを使って表示させたいので以下を設定ファイルに加えます。
(長いので別ファイルに保存して Vim の :sourceコマンドで引っ張ってきてもいいと思います)

init.vim
fus:snr() abort
    returnmatchstr(expand('<sfile>'),'.*\zs<SNR>\d\+_')
endfu
lets:snr=get(s:,'snr',s:snr())letg:fzf_layout={'window':'call '..s:snr..'fzf_window(0.9, 0.6, "Comment")'}fus:fzf_window(width, height, border_highlight) abort
    let width =float2nr(&columns * a:width)let height =float2nr(&lines * a:height)let row =float2nr((&lines- height) / 2)letcol=float2nr((&columns- width) / 2)lettop='┌'.repeat('─', width -2).'┐'let mid ='│'.repeat(' ', width -2).'│'let bot ='└'.repeat('─', width -2).'┘'let border =[top]+repeat([mid], height -2)+[bot]ifhas('nvim')let frame =s:create_float(a:border_highlight,{            \'row': row,            \'col':col,            \'width': width,            \'height': height,            \})call nvim_buf_set_lines(frame,0,-1,v:true, border)calls:create_float('Normal',{            \'row': row +1,            \'col':col+2,            \'width': width -4,            \'height': height -2,            \})
        exe 'au BufWipeout <buffer> bw '..frame
    elselet frame =s:create_popup_window(a:border_highlight,{            \'line': row,            \'col':col,            \'width': width,            \'height': height,            \'is_frame':1,            \})callsetbufline(frame,1, border)calls:create_popup_window('Normal',{            \'line': row +1,            \'col':col+2,            \'width': width -4,            \'height': height -2,            \})endif
endfu

fus:create_float(hl, opts) abort
    let buf = nvim_create_buf(v:false,v:true)let opts =extend({'relative':'editor','style':'minimal'},a:opts)letwin= nvim_open_win(buf,v:true, opts)callsetwinvar(win,'&winhighlight','NormalFloat:'..a:hl)return buf
endfu

fus:create_popup_window(hl, opts) abort
    ifhas_key(a:opts,'is_frame')let id =popup_create('', #{            \line:a:opts.line,            \col:a:opts.col,            \ minwidth:a:opts.width,            \ minheight:a:opts.height,            \ zindex:50,            \})callsetwinvar(id,'&wincolor',a:hl)
        exe 'au BufWipeout * ++once call popup_close('..id..')'returnwinbufnr(id)elselet buf =term_start(&shell, #{hidden:1})callpopup_create(buf, #{            \line:a:opts.line,            \col:a:opts.col,            \ minwidth:a:opts.width,            \ minheight:a:opts.height,            \ zindex:51,            \})
        exe 'au BufWipeout * ++once bw! '..buf
    endif
endfu

すると以下のように表示されます。
image.png

注意点

coc.nvimの拡張 coc-listsなどで追加された機能については実装されていません。
ぼくは必要になったら手動で :CocListをしています。

感想

fzf 軽快でいいですね。日々の相棒のエディタからストレスをなくしていくのは重要だと思っているので、こういった痒いところに手が届くプラグインが作られているのは大変ありがたいです。

【Vim】カーソルキーでの移動を禁止する設定(ドラゴン付き)。

$
0
0

事前インストール

cowsayとslコマンド必要なので予めインストールが必要
brew install cowsay sl

設定方法

.vimrcに以下を設定する。

設定するとAAのドラゴンが丁寧に教えてくれる。

function TeachKey(message)let summon =join([!clear; cowsay -f dragon “,a:message,“を押すのだ”])
    exec summon
endfun
nnoremap <buffer><Left><Esc>:call TeachKey(h)<CR>
nnoremap <buffer><Right><Esc>:call TeachKey(l)<CR>
nnoremap <buffer><Up><Esc>:call TeachKey(k)<CR>
nnoremap <buffer><Down><Esc>:call TeachKey(j)<CR>
nnoremap <buffer><PageUp><Esc>:call TeachKey(‘Ctrl+B’)<CR>
nnoremap <buffer><PageDown><Esc>:call TeachKey(‘Ctrl+F’)<CR>
inoremap <buffer><Left><Esc>:call TeachKeyBi)ding(h)<CR>
inoremap <buffer><Right><Esc>:call TeachKey(l)<CR>
inoremap <buffer><Up><Esc>:call TeachKey(k)<CR>
inoremap <buffer><Down><Esc>:call TeachKey(j)<CR>
inoremap <buffer><PageUp><Esc>:call TeachKey(‘Ctrl+B’)<CR>
inoremap <buffer><PageDown><Esc>:call TeachKey(‘Ctrl+F’)<CR>
vnoremap <buffer><Left><Esc>:call TeachKeyBi)ding(h)<CR>
vnoremap <buffer><Right><Esc>:call TeachKey(l)<CR>
vnoremap <buffer><Up><Esc>:call TeachKey(k)<CR>
vnoremap <buffer><Down><Esc>:call TeachKey(j)<CR>
vnoremap <buffer><PageUp><Esc>:call TeachKey(‘Ctrl+B’)<CR>
vnoremap <buffer><PageDown><Esc>:call TeachKey(‘Ctrl+F’)<CR>

nnoremap <buffer><Del><Esc>:<C-u>!sl<CR>
inoremap <buffer><Del><Esc>:<C-u>!sl<CR>
vnoremap <buffer><Del><Esc>:<C-u>!sl<CR>setbackspace=0

確認方法

設定されるとこんなのが出る。

image.png

参考URL

vi(vim)で完全にカーソルキーを無効化させたい - スタック・オーバーフロー

development/vim

MacvimでTerminusを使いたい

$
0
0

目次

  • Mac で Terminusをインストール
  • vimrc or gvimrcに設定を入力

MacでTerminusをインストール

Homebrewがインストール済であることが前提です。
インストール方法

ターミナルで以下の文を入力

brew tap homebrew/cask-fonts
brew cask install font-terminus

vimrc or gvimrcに設定を入力

ターミナルで以下の文を入力

vim ~/.vimrc

or

vim ~/.gvimrc

開けたら設定を記入するのですが、ここがややこしくて苦戦しました。
Terminus (TTF)というフォント名なので

setguifont=Terminus (TTF) Bold:h任意の大きさ

となるのかなと記入しましたが反映されない!!
TerminusをFontBookで確認すると下のようになっています。(これはボールドです)

PostScript名 TerminusTTF-Bold
正式名称 Terminus (TTF) Bold
ファミリー Terminus (TTF)
スタイル ボールド
種類 TrueType

正式名称ではダメだったのでPostScript名で記入してみると今度は成功!
下にミディアム、イタリック、ボールド、ボールドイタリックの記入例を載せておきます。

ミディアム

setguifont=TerminusTTF:h任意の大きさ

イタリック

setguifont=TerminusTTF-Italic:h任意の大きさ

ボールド

setguifont=TerminusTTF-Bold:h任意の大きさ

ボールドイタリック

setguifont=TerminusTTF-Bold-Italic:h任意の大きさ

以上です。

VIMの設定

$
0
0

VIMを使いやすいように設定してみる

ヘタレなので、キー移動にhjklとか覚えてられないので、矢印キーとBackSpaceキーを使えるようにしてみる。

設定

command
echo set nocompatible >> ~/.vimrc
echo set backspace=indent,eol,start >> ~/.vimrc

以上です。


E45: 'readonly' option is set (add ! to override) が出た場合に保存する方法

$
0
0

E45: 'readonly' option is set (add ! to override)が出たら

パニックになったのでメモ。

  • 入力モードをescで抜ける

  • コマンドモードで下記を実行

:w !sudo tee %
  • 強制脱出
:q!

上書き完了

Vimのキーバインドを体系的に覚えたい

$
0
0

Vimのキーバインドはやっぱり便利

インフラ系だと手元にViしかなく触らなければならないことがあるのはもちろんのこと、その他の機会でもやっぱりVimのキーバインドは便利なのでVimっぽく操作できたらいいのに。。みたいな機会は多いと思います。実際筆者もVScodeを使いつつ、キーバインドはVimのものを利用しています。

しかしながら、その種類が多くて覚えにくかったり体系的にまとまっているものが少なかったので、自分用にまとめました。

これは最初に覚える

  • !は強制の意味。強制的に保存とか、強制的に終了とか。
  • ZZ保存して閉じる。
  • wは書き込み、qは終了、eはファイルを開く。
  • .は直前の操作を繰り返す。
  • Ctrl+gでファイル情報の閲覧
  • uはUndo。
  • Ctrl+rはReDo。
  • Ctrl+nは後方補完。(前方補完はCtrl+p

選択系コマンド

効率的にビジュアルモードを使いこなしましょう。なおViにはこの概念はありません。

  • vは領域選択スタート
  • Vは行選択スタート
  • Ctrl+vは矩形選択スタート
  • yを押せばヤンク、dを押せば消せるけど、1行しか処理しない時には、ダブルコマンドするとよい。

移動系コマンド

テキスト編集の肝は「移動」して「選択」だから、以下のコマンドもかなり重要。

  • ^は行頭に移動(本当の行頭はO、これは非空白文字までの移動)
  • $は行末に移動
  • ggはページ最初に移動(ページ最後に移動はG
  • Hはその画面の最初に移動(Lは画面の最後に)
  • {は空白行を目印に移動(小かっこ・中かっこならなんでもOK)
  • %は対応するかっこまで飛ぶ。
  • 3gjは3行下に移動。
  • wは単語ごとに前に移動(うしろへ移動はb
  • (は現在の文の先頭へ。

「選択」して「処理」まで一括系コマンド

文字や文章の削除をしたいときにわざわざ選択してから、処理のコマンドを更に打ち込むのは大変なので、よく使うものに対しては割り当てられています。

  • Dはカーソル位置から行末まで削除
  • ddはカーソルのある行を切り取り。
  • ~は小文字と大文字を相互に変換
  • *はカーソルのある単語を検索
  • /は下方検索(上方検索なら?
  • Ctrl+aはインクリメント(xならデクリメント)
  • xは1文字だけ消すことができる(Shiftを押しながらはBackspaceと同等)
  • 3ddは3行消す。
  • yyでカーソルのある行をヤンク。
  • pは貼り付け。
  • >>はインデント。
  • =は範囲を自分で選んで自動インデント。
  • Jはカーソル行のある行を次の行と結合。

マーク機能

  • 簡単に言うと現在地の登録と登録したポイントへのジャンプ機能。
  • :marksでマーク箇所一覧。
  • m[a-zA-Z]でマーキング。
  • :b [1-99]で番号指定してバッファを開くことができる。
  • :bw [1-99]でバッファを完全に削除
  • :bd [1-99]:ls!では残っているがバッファに関連する変数やマーカーを消すだけ。
  • :delmarks!はマークの一括削除。
  • Ctrl+oで古いマークへ移動
  • Ctrl+iで新しいマークへ移動。
  • \ [a-zA-Z]で移動できるの忘れがち。
  • こういうファイルの編集情報はviminfoに入っているよ。

アウトライン機能

  • 領域選択してzfでたためる。スペースで開ける。
  • zoで開く、zOですべて開く、zcで閉じる、zCですべて閉じる。

コマンド

ちょっと込み入った操作や、Vim全体の設定に関わる(=つまりテキストの操作以外の部分)はコマンドを実行して行います。

  • :split(spも可)は水平画面分割。
  • :vsplit(vstも可)は垂直画面分割。
  • :closeは今開いている画面分割を閉じることができる。
  • :bn:bfでバッファをいったりきたり、いま開いているバッファは:lsで閲覧することが可能。
  • :pwdでカレントディレクトリ表示
  • :regでレジスタに入っているもの一覧、呼び出しは"2pみたいな感じで
  • :%s/置換前/置換後/gは定型文として覚えよう。%はファイル全体に適用の意。本来の:sは一部にのみ適用される置換コマンド。は1個だけ置換ではなくすべて置換の意味。最後のオプション値はgcにすると置換をいちいち確認してくれる。
  • ウィンドウ分割状態で:qは消える。:hideはウィンドウを閉じるけどバッファにはのこる。
  • ファイルをエンコーディングも可能。:e ++enc=cp932
  • :waは開いているファイルをすべて保存。
  • :Exはvim標準のエクスローラを開く。

Vimを使い倒すための中級編

ここから先は上の基本コマンドを使いこなせるようになってから進めてみてください。

奥が深いね、挿入モード

Vimには挿入モードというモードがあり、いわゆる普通のテキストエディタのカーソルを移動して書き込む画面がVimでは挿入モードに相当します。つまりVimのニュートラルな画面はカーソルが画面上をスケートのように滑って選択ができる画面です。なんでしょうPhotoshopでエフェクトをかける範囲をまず選択して、編集コマンドを差し込むのと同じ感覚です。

  • iはカーソル位置から入力開始。
  • aはカーソルの次の位置から入力開始。
  • oはカーソルの次の行の先頭から入力開始。
  • Iは行の先頭から入力開始。
  • Aは行の終わりから入力開始。
  • Oはカーソルの前の行の先頭から入力開始。

ちなみに、挿入モードではCtrl+tCtrl+dでインデントができます。

テキストオブジェクトとオペレータ

Vim特有のテキストオブジェクトのイメージは、チャンクとか単語単位でメソッドを適用すれば作業効率あがるよねって感じです。

  • オペレータはc(切り取って挿入モード)とかd(切り取り)とかy(コピー)とか。
  • di"とすると"~"の中身だけ切り取り。da"だと括弧ごと切り取り。
  • テキストオブジェクトのiはinnnerで中身の、aはa wordで全体という意味。

MacVimでウェイトの軽いフォントを使えるようになってチョーしあわせなんですけど。

$
0
0

背景

MacVimのフォントを、ウェイトを軽めにしたかったのだけど、指定する「名前」がわからなくて困っていた。

Edit > Font > Show Fonts で選んでも、うまく指定できないし、.gvimrcに書いても「ダメ」って言われるし。

できたこと

  • 日本語は、ヒラギノ角ゴシック W1
  • ラテン文字は、Iconsolataのライト

できあがりはこんなかんじ。チョーしあわせ。

スクリーンショット 2020-11-08 16.33.50.png

ちなみにbeforeはこんなかんじ。スクショではあんまりわからんか……。画面で見ているとずいぶんちがう。

スクリーンショット 2020-11-08 16.47.24.png

拡大してみる。
before:
A-z.png

after:
B-z.png

圧迫感が弱まったのがうれしい。

gvimrcはこんなかんじ。

gvimrc
setguifont=Inconsolata\ Regular\ Light:h18
setguifontwide=ヒラギノ角ゴシック\ W1:h16

わかったこと

  • フォント名の空白は、\でエスケープする
  • フォント名は、Font Book.appで調べる

fb.png

 ↓

fbのコピー.png

いや、これで、フォント選びが楽しくなるわー。

環境など

MacVim Custom Version 8.2.1719 (166) brew caskでインストール
macOS Catalina バージョン 10.15.7(19H2)
MacBook Pro (13-inch, 2020, Four Thunderbolt 3 ports)

カラースキームはNord

フォント選びは、結城浩先生の「ヒラギノ明朝+Inconsorata」のマネ。

Vim script で湯婆婆を実装してみる

$
0
0

はじめに

この記事は暇つぶしに書いたネタです。

コード

yuba-ba.vim
lets:Keiyakusho=function("input",["契約書だよ。そこに名前を書きな。\n"])lets:name=s:Keiyakusho()
echo "\nフン。"..s:name.."というのかい。贅沢な名だねぇ。"lets:name_index=rand()%strchars(s:name)lets:new_name=strcharpart(s:name,s:name_index,1)

echo "今からお前の名前は"..s:new_name.."だ。いいかい、"..      \s:new_name.."だよ。分かったら返事をするんだ、"..s:new_name.."!"

こちらがコードです。
ファイルに保存し、こちらのコードを :so yuba-ba.vimとすれば動きます。

コードの解説

契約書

Vim scriptでは input()関数を用いてプロンプトから文字列を取得できます。 :h input()
これを、 function()関数を用いて部分適用し、セリフを言い名前を聞く s:Keiyakushoを作ります。

文字列の連結は .または ..で行います。
.は辞書の参照 my_dict.key等と被り可読性を落とすので、新しい..を用いました。

lets:Keiyakusho=function("input",["契約書だよ。そこに名前を書きな。\n"])lets:name=s:Keiyakusho()
echo "\nフン。"..s:name.."というのかい。贅沢な名だねぇ。"

名前を奪う

新しい名前を決定するために乱数が必要でした。
:h rand()を調べたところ存在した(!?)ので、こちらの関数を用いることにしました。

また、最大値を指定するために文字数を取得する strchars()関数を利用しました。Vimには strlen()関数がありますが、マルチバイト文字には対応していません。

lets:name_index=rand()%strchars(s:name)lets:new_name=strcharpart(s:name,s:name_index,1)

実行結果

スクリーンショット 0002-11-08 午後7.19.30.png

まとめ

  • 部分適用なのかわからない
  • github

ターミナルの背景色を取得する

$
0
0

はじめに

CUI/TUIアプリケーションを書いていると、実行中のターミナルの背景色を知りたいことがあります。
大抵の場合、自分が使っている黒背景を基準に色を決めてしまうのですが、広く使われるようになってくると
白背景のターミナルの人から「見づらいのでデフォルトの色を変えてほしい」といった要望が来ることもあります。

歴史あるターミナルアプリケーション(例えばvimやEmacs)は、実は様々な方法でターミナルの背景色を取得したり推定したりしています。この記事ではそれらの方法と2020年時点での各ターミナルの対応状況をまとめます。

最後に今回調査した結果を元にRustのライブラリを実装したのでそちらも紹介します。

調査する上で以下の記事が大変参考になりました。

また、最新のターミナル対応状況調査につきましてては

の皆様にご協力頂きました。ありがとうございます。

背景色の取得方法

ここではターミナル背景色の取得方法を紹介します。
これらはターミナルが対応していれば確実に取得できますが、非対応な場合全く情報は得られません。

XTerm Control Sequences

これは元々XTermというターミナルが実装していた方法で「特殊なシーケンスをターミナルに出力すると、背景色の情報がターミナルから返ってくる」というものです。

XTerm Control Sequences

詳細仕様は上記にありますが、簡単に言うと以下のようなシーケンスをprintfすると背景色が得られます。

$printf"\x1b]11;?\x1b\\"11;rgb:0000/0000/0000

また、tmuxやGNU Screenといったターミナルマルチプレクサを使っていると、上記のシーケンスはそのままターミナルに届かないので若干エスケープしてやる必要があります。

tmux
$ printf "\x1bPtmux;\x1b\x1b]11;?\x07\x1b\\"
11;rgb:0000/0000/0000
screen
$printf"\x1bP\x1b]11;?\x07\x1b\\"11;rgb:0000/0000/0000

当初XTermといくつかのターミナルだけが対応していた方式のようですが、2020年現在、かなりの数のターミナルが対応するようになってきています。

WIN32API

これはWindowsのWin32 Console(いわゆる「コマンドプロンプト」)の背景色を取得する方法です。

GetConsoleScreenBufferInfo

WIN32APIにあるGetConsoleScreenBufferInfoという関数で、ターミナルの情報を取得でき、そこから背景色を得ることができます。

背景色の推定方法

以下ではターミナル背景色の推定方法を紹介します。
取得方法に対応していない場合でも、比較的可能性の高い値を得るために使われます。

COLORFGBG環境変数

これは元々rxvtというターミナルで前景・背景色を設定する方法でした。iTerm2などもこれを設定するようです。
この環境変数は 0;15のように;区切りで前景と背景を0-15の16通りから指定します。この16通りはデフォルトでは以下のWikipediaに載っているような割り当てになっているので、それに基づいて背景色を得る方法になります。
(例えばvimでは0-6と8を黒背景と推定します)
しかし、実際にはこの割り当ては設定で変更可能なので、必ずしも正しい背景色を得られるわけではなさそうです。

https://en.wikipedia.org/wiki/ANSI_escape_code#3-bit_and_4-bit

TERM環境変数

TERM環境変数には使っているターミナルの情報が入っているのでそれに基づいて背景色を推定する方法です。
(実際には必ずしも正しいターミナルの情報が入っているわけではないですが…)

例えばvimではTERMputtycygwinだった場合に黒背景と推定します。
これらのターミナルでも背景色を設定することは可能であり、単に「デフォルトが黒だから黒の可能性が高いだろう」という程度の根拠と思われます。

OS情報

OSの情報から背景色を推定することもあります。
例えばvimではMS-DOSやOS/2の場合黒背景と推定します。
(これらのOSで背景色が変更可能なのかどうかは知らないので、もしかしたら確定なのかもしれません)

最新のターミナル対応状況

取得方法のところで説明した通り、現状確実に取れそうなのはXTerm Control SequencesとWIN32APIだけです。
このうちWIN32APIは当然Win32 Consoleしか対応していないので実質的にはXTerm Control Sequences一択となります。
このXTerm Control Sequencesについて各ターミナルの対応状況を調べてみました。

ターミナル対応/非対応
Alacrittyo
GNOME Terminalo
GNU Screeno
iTerm2o
LilyTermx
macOS terminalo
MATE Terminalo
minttyo
Poderosax
PuTTYx
QTerminalx
RLogino
rxvt-unicodeo
sakurao
Tera Termo
Terminatoro
tmuxo
Windows Terminalx
xfce4-terminalo
xtermo

このように全てというわけではないものの、多くのターミナルが対応するようになってきています。
最近ユーザが増えていると思われるWindows Terminalですが、こちらはIssueが立っており、対応が期待されます。

Rustライブラリ

ここで紹介した背景色の取得方法を実装したRustライブラリです。

https://github.com/dalance/termbg

以下のようにrgb関数でRGB値を、theme関数でLight/Darkのどちらかを取得できます。

fnmain(){lettimeout=std::time::Duration::from_millis(100);letrgb=termbg::rgb(timeout);lettheme=termbg::theme(timeout);matchrgb{Ok(rgb)=>{println!("  Color: R={:x}, G={:x}, B={:x}",rgb.r,rgb.g,rgb.b);}Err(e)=>{println!("  Color: detection failed {:?}",e);}}matchtheme{Ok(theme)=>{println!("  Theme: {:?}",theme);}Err(e)=>{println!("  Theme: detection failed {:?}",e);}}}

Rust製のCUI/TUIであればそのままライブラリ呼び出しで使えばいいですし、
これを使ったコマンドラインツールを作ってシェルスクリプトから呼び出すような使い方も可能です。

Raspberry Pi での Editor の変え方

$
0
0

Editorを変える

crontab -e

で変な編集画面(nano?)が表示されて困った。

sudo update-alternatives --config editor

数値を入力してEditorを選べば完了。

vimテキストオブジェクト(風に振舞うキーマップ)の自作(手習い).md

$
0
0

概要

前回のオペレータの自作から引き続き、今度はテキストオブジェクトを自作してみようと思います。

目指すは、以下の2つです。

  • テキストオブジェクトの1文字目としての sを実現する。(surround.vim 的な)
  • テキストオブジェクトの2文字目としての %を実現する。(:h matchpairs参照)

環境

GVIM 8.2.1287 (kaoriya 2020/07/24)

テキストオブジェクトの自作

巷には、テキストオブジェクトを追加するプラグインや、テキストオブジェクトの追加を容易にするためのプラグインなどが存在しています。

・・・が、あえてそれらは使いません。

それらのコードを読んだりもしていないので、自分が正しいことをやっている自身もありませんが、とりあえずそれっぽく実現できてしまったので、その過程を記事として書いていこうと思います。

※ドットリピートの実装方法だけ、repeat.vim をちょっとカンニング参考にさせてもらいました。

・・・職場は基本ネットに繋がらないので、プラグインマネージャを使用するなど夢のまた夢なのです。
スクリプトとして手打ちできる程度に、なるべくコンパクトに実装する必要があります。

人様の事を慮って、外部定義によりカスタマイズ可能にするような冗長性も必要ありません。
だって全部自作で使うのも自分だから。カスタマイズというよりも、もう直接修正します。

前回の「最後に」、にも書きましたが、オペレータを追加する方法としては、:h 'operatorfunc'というものが用意されていました。とても便利でした。

ですが、テキストオブジェクトの場合には、そういった優しい仕掛けは存在しないようなのです?

なので、オペレータ待機モードでマッピングを行って、自前で処理を書く必要があるのでは?と思い、:h map.txtを読むところから始めました。

まずは定番の、Vimヘルプの受け売り

まずは、:h omap-infoのセクションを読んでいたところ、このようなサンプルを発見しました。

onoremap <silent> F :<C-U>normal! 0f(hviw<CR>

オペレータ待機モードにマッピングを追加する。まさに私がやりたかったことそのものです。
相変わらずいいところを突いてくるニクいサンプルのチョイスで痺れます。

さっそく試してみました。

実際に試してみたい場合は、以下の手順を実行してください。

  • 以下の、omap_test.vimを適当なパスに保存して、Vimで直接開いてから、:so %する。
  • fu! s:hoge()の行のどこかで、cfとかdfとかvfとかしてみてください。
  • もしくは、fu! s:hoge()の行のどこかで、cFとかdFとかvFとかしてみてください。
omap_test.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker :" ヘルプ受け売りそのまま(:h omap-info)"関数名部分を選択。
onoremap <silent>f:<C-U>normal!0f(hviw<CR>"上記の動きを、visualモード時にも行うように。
vnoremap <silent>f:<C-U>normal!0f(hviw<CR>"上記を応用して、大文字バリエーション追加(括弧直前の1Wordではなく、空白から括弧までを対象)
onoremap <silent> F :<C-U>normal!0f(hBvt(<CR>
vnoremap <silent> F :<C-U>normal!0f(hBvt(<CR>fu!s:hoge()
    echo '上の行でdfは"hoge"が削除。cやyも使えるし、vfなら"hoge"が選択される'
    echo '大文字のFなら、"s:hoge"が対象になる。'endf

・・・実際にはこんなものを定義してしまうと、f{char}による行内の右方向検索が効かなくなってしまって、ビックリするほど超不便になるだけなので、あくまでサンプルとして見るようにしてください。

確かに、関数定義の行のどこにカーソルが存在していても、関数名を対象にオペレータが効くようになります。
cfで関数名(hoge)が削除されてインサートモードに。
cFで関数名(s:hoge)が削除されてインサートモードに。
dfで関数名(hoge)が削除される。
dFで関数名(s:hoge)が削除される。
・・・みたいな。

vmapにも同じものを追加することで、v{motion}のような振る舞いも実現できました。
関数定義の行のどこにカーソルが存在しても、vfとやれば関数名部分がヴィジュアル選択されます。

というかマップの右辺値でやっていることはヴィジュアル選択なので、こちらの方がより直観的で素直なマッピングの発現なのでは?と思ったり。

因みにマッピング右辺値の<CR>が無いと、omapでもvmapでも、何も起こりません。

ヘルプのサンプルを試してみてわかったこと。

  • オペレータ待機モードで、ビジュアル選択して<CR>するようなマッピングを行うと、オペレータがその範囲を対象に処理を行ってくれる。
  • 不用意にマッピングを弄って、意図せず標準の機能を損なった場合、使用者の受ける衝撃と混乱は想像を絶する。

という事が分かった。

とくに2つ目は本当にビックリしましたね。

「?(あ、なんか変なタイポしたかも?)・・・!(またか!)・・・!?(いや何かおかしい!?)・・・??(え、なにこれ、え??)・・・!!(ああ!!)」

という感じでした。久々に強烈なアハ体験のような衝撃を脳に受けました。

1度目の挫折

ここまで試したところで、オペレータ待機モードのマッピングでは、一つ目の目的である「テキストオブジェクトの1文字目s」を定義するには、もう手遅れなのではないか?という疑問が生じました。

例えば、dsを定義したい場合、dで既にオペレータ待機モードに入っているのですが、そこにsをマッピングするとします。

関数を割り振るとしたら、こんな感じになります。

omap s :<C-U>call <SID>surround()<CR>

で、問題は、cの後のsだろうが、dの次のsだろうが、yの次のsだろうが、全部surround()が処理をしないといけなくなります。

csの次に入力されるのは、変更したい現在の囲み文字と、変更後の囲み文字の2つです。
dsの次に入力されるのは、削除したい現在の囲み文字です。
ysの次に入力されるのは、囲みたいテキストオブジェクトと、追加したい囲み文字です。

範囲選択しただけで、1文字目のcdyが発動するようなomapに乗せる関数では、上記のような仕掛けのために用いることは難しそうです。

そもそも、ysはyankの働きをしないので、yオペレータとして待機している時点で待ったをかける必要があります。

surround.vim の真似をしてみる。

という事でmapに、csdsysを書いてしまおうという方針転換をしました。
※本来はnmapでやるべきなのでしょうが、この時点の筆者はまだ気が付いていませんでした。。

その1

ひとまずガーッと書いてみました。

  • ddが遅延するのが気持ち悪い! (noremapのせいです)
  • ドットリピートができない! (g@は何も考えなくてもドットリピートできたのに・・・)
  • ysの動きが変! (この時はまだ気がついてもなかった)

という問題点を抱えたバージョンです。
生まれたての小鹿のようなものです。微笑ましいですね。

omap_test2.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker :"互換性オプションを退避して規定値に設定lets:save_cpo=&cposetcpo&vimnoremap<silent>cs:<C-U>call<SID>cSorround()<CR>noremap<silent>ds:<C-U>call<SID>dSorround()<CR>noremap<silent> ys :<C-U>call<SID>ySorround()<CR>fu!s:cSorround() abort "{{{calls:sorround()let after =nr2char(getchar())calls:getAfter(after)callsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    exec "normal! c".s:afterEndcallsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal!h
    exec "normal! c".s:afterStartendf "}}}fu!s:dSorround() abort "{{{calls:sorround()callsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    normal!dcallsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal! hd

endfu "}}}fu!s:ySorround() abort "{{{calls:sorround()let after =nr2char(getchar())calls:getAfter(after)callsetpos('.',s:iEnd)
    exec "normal! a".s:afterEndcallsetpos('.',s:iStart)
    exec "normal! i".s:afterStartendf "}}}fu!s:getAfter(after) abort "{{{let flgMps =0formpinsplit(&mps,',')let pairs =split(mp,':')ifa:after== pairs[0]||a:after== pairs[1]let flgMps =1breakendifendforif flgMps ==0lets:afterStart=a:afterlets:afterEnd=a:afterelselets:afterStart=pairs[0]lets:afterEnd=pairs[1]endifendf "}}}fu!s:sorround() abort "{{{let what =nr2char(getchar())"すでにビジュアルモードの場合は抜ける。ifmode()==? "v"
        exec "normal! ".mode()endif

    exec "normal vi".what
    lets:iEnd=getcurpos()
    normal!olets:iStart=getcurpos()
    normal!v

    exec "normal va".what
    lets:aEnd=getcurpos()
    normal!olets:aStart=getcurpos()
    normal!v"範囲確認用"message clear"echom s:aStart[2]."-".s:iStart[2]."(".s:iStart[1].") , ".s:iEnd[2]."-".s:aEnd[2]."(".s:iEnd[1].")"endf "}}}"互換性オプションを復元let&cpo=s:save_cpo"test"<a>'(hoge )'</a>"<a>'(hoge )'</a>

キモは、s:surround()関数です。
getchar()により1文字入力を受け付けて、テキストオブジェクトの2文字目に挿げ替えて使用します。
例えば、csに続けてwが打たれた場合は、viwvawを行い、それぞれの選択開始・終了位置を保持します。

次にgetAfter()関数です。
こちらは、単一括り文字以外で、組になる括り文字の相方を探すための処理になります。
括弧などの組み合わせについては、:h 'matchpairs'を参照です。
vimrcに全角括弧系をvimrcに追加していたので、それも含めて処理してくれるようにしてあります。

ここまでできれば、もう完成したも同然でした。

まだこの時点では、ysの動きは本家と異なっています。
本家だとysiw'とするべきところが、ysw'となります。
ysaw'相当の振る舞いはまだ実現できていませんでした。

そんなことよりも、こんなに簡単にできちゃった!という喜びが、ドットリピートができない!と言うことに気づいた瞬間、絶望に塗り替えられました。

その2

無我夢中にドットリピートに対応させました。
noremapからnnoremapに変更しました。dd等が遅延して気持ち悪い問題も解決しました。
ysの挙動が変なのは、この時点ではまだ気付いてもいないので相変わらずです。

omap_test3.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker :scriptencoding utf-8"二重ロード避けifexists("g:loaded_omap_test3")finishendifletg:loaded_omap_test3=1"互換性オプションを退避して規定値に設定lets:save_cpo=&cposetcpo&vimauTextChanged,TextChangedI,TextChangedP,TextYankPost * call<SID>textChanged()fu!s:textChanged() "{{{ifexists("b:dotReady")letb:dotReady=(b:dotReady==0 ? 0:eval(b:dotReady-1))endifendf "}}}"nnoremapではなく noremap にしてしまうと、ddとかが遅延発動して気持ち悪くなるので注意。
nnoremap <silent>cs:<C-U>call<SID>cSorround(v:false)<CR>
nnoremap <silent>ds:<C-U>call<SID>dSorround(v:false)<CR>
nnoremap <silent> ys :<C-U>call<SID>ySorround(v:false)<CR>fu!s:cSorround(dotRep) abort "{{{letb:dotRepeatFunc="cSorround"calls:sorround(a:dotRep)ifa:dotRep==v:true && exists("b:after")let after =b:afterelselet after =nr2char(getchar())letb:after= after
    endifcalls:getAfter(after)callsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    exec "normal! c".s:afterEndcallsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal!h
    exec "normal! c".s:afterStartletb:dotReady=2endf "}}}fu!s:dSorround(dotRep) abort "{{{letb:dotRepeatFunc="dSorround"calls:sorround(a:dotRep)callsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    normal!dcallsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal! hd

    letb:dotReady=2
endfu "}}}fu!s:ySorround(dotRep) abort "{{{letb:dotRepeatFunc="ySorround"calls:sorround(a:dotRep)ifa:dotRep==v:true && exists("b:after")let after =b:afterelselet after =nr2char(getchar())letb:after= after
    endifcalls:getAfter(after)callsetpos('.',s:iEnd)
    exec "normal! a".s:afterEndcallsetpos('.',s:iStart)
    exec "normal! i".s:afterStartletb:dotReady=2endf "}}}fu!s:getAfter(after) abort "{{{let flgMps =0formpinsplit(&mps,',')let pairs =split(mp,':')ifa:after== pairs[0]||a:after== pairs[1]let flgMps =1breakendifendforif flgMps ==0lets:afterStart=a:afterlets:afterEnd=a:afterelselets:afterStart=pairs[0]lets:afterEnd=pairs[1]endifendf "}}}fu!s:sorround(dotRep) abort "{{{ifa:dotRep==v:true && exists("b:what")let what =b:whatelselet what =nr2char(getchar())letb:what= what
    endif"すでにビジュアルモードの場合は抜ける。ifmode()==? "v"
        exec "normal! ".mode()endif

    exec "normal vi".what
    lets:iEnd=getcurpos()
    normal!olets:iStart=getcurpos()
    normal!v

    exec "normal va".what
    lets:aEnd=getcurpos()
    normal!olets:aStart=getcurpos()
    normal!v"範囲確認用"message clear"echom s:aStart[2]."-".s:iStart[2]."(".s:iStart[1].") , ".s:iEnd[2]."-".s:aEnd[2]."(".s:iEnd[1].")"endf "}}}

nmap .:<C-U>call<SID>dotRepeat()<CR>fu!s:dotRepeat() "{{{ifexists("b:dotReady")&& b:dotReady!=0
        exec "call <SID>".b:dotRepeatFunc."(".v:true.")"else
        normal!.endifendf "}}}"互換性オプションを復元let&cpo=s:save_cpo"test"<a>'(hoge )'</a>"<a>'(hoge )'</a>

ドットリピートについては、dotRepeat()関数がキモになります。

  • csdsysが押されたときに、フラグを立てておいて、テキストが変更されたときそのフラグを下げる。
  • .押下時にそのフラグが立っている時は、さっき実行したのと同じ関数を実行する。 という感じで実装しました。

1を立てておくと、自分自身が編集した事で発生するイベントで0になってしまうので、2を設定してあります。

実はまだ問題が残っており、アンドゥを挟むと、次のドットリピートが効かなくなります。
cs'"してから、.で繰り返していって、間違えたと思ったらuして、再度.すると、またcs'"が続いていってほしいじゃないですか。

続かないんですよ、これが。

という事で、「その3」に続きます。

その3

このバージョンでは以下の対応をしました。

  • アンドゥされた後にもドットリピートが続くようにしました。
  • ysが受け取るものを、サラウンドオブジェクトではなく、テキストオブジェクトに変えました。
omap_test4.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker :" sorround.vimの真似事。ysの動きも合わせたつもり。タイポ時はu頼み。" 後は、追加する囲みに、タグ<hoge>~</hoge>を対応させたい。scriptencoding utf-8"二重ロード避けifexists("g:loaded_omap_test4")finishendifletg:loaded_omap_test4=1"互換性オプションを退避して規定値に設定lets:save_cpo=&cposetcpo&vimauTextChanged,TextChangedI,TextChangedP,TextYankPost * call<SID>textChanged()fu!s:textChanged() "{{{ifexists("b:dotReady")"テキストが変更されたときに、-1する。letb:dotReady=(b:dotReady==0 ? 0:eval(b:dotReady-1))endifendf "}}}"nnoremapではなく noremap にしてしまうと、ddとかが遅延発動して気持ち悪くなるので注意。
nnoremap <silent>cs:<C-U>call<SID>cSorround(v:false)<CR>
nnoremap <silent>ds:<C-U>call<SID>dSorround(v:false)<CR>
nnoremap <silent> ys :<C-U>call<SID>ySorround(v:false)<CR>fu!s:cSorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="cs""囲い部分の範囲を取得calls:getSorround(a:dotRep)"変更後の囲い文字を取得ifa:dotRep==v:true
        "ドットリピート時は前回値を使用let after =b:afterelseredraw!
        echo s:cmd.b:whatlet after =nr2char(getchar())letb:after= after
    endif"囲い末尾の取得calls:getAfter(after)"囲い終わり箇所の置き換えcallsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    exec "normal! c".s:afterEnd"囲い始め箇所の置き換えcallsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal!h
    exec "normal! c".s:afterStart"自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2redraw!endf "}}}fu!s:dSorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="ds""囲い部分の範囲を取得calls:getSorround(a:dotRep)"囲い終わり箇所を削除callsetpos('.',s:iEnd)
    normal!lvcallsetpos('.',s:aEnd)
    normal!d"囲い始め箇所を削除callsetpos('.',s:aStart)
    normal!vcallsetpos('.',s:iStart)
    normal! hd

    "自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2redraw!
endfu "}}}fu!s:ySorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="ys""囲う部分のテキストオブジェクト部分を取得calls:getMotion(a:dotRep)"追加する囲い文字を取得ifa:dotRep==v:true
        "ドットリピート時は前回値を使用let after =b:afterelseredraw!
        echo s:cmd.b:count.b:where.b:whatlet after =nr2char(getchar())letb:after= after
    endif"変更後の(追加する)囲い文字を取得calls:getAfter(after)"囲い終わり箇所に追加callsetpos('.',s:iEnd)
    exec "normal! a".s:afterEnd"囲い始め箇所に追加callsetpos('.',s:iStart)
    exec "normal! i".s:afterStart"自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2redraw!endf "}}}fu!s:getAfter(after) abort "{{{let mps_save =&mps"アングルブラケットをマッチペアに追加 "TODO:「yaw<a>」で「hoge⇒<a>hoge</a>」みたいなのに対応する時には外す。"sの直後としては<:>はありだが、変更後としては<はタグの開始で、>のみの場合に、<:>括りとして扱うように。setmps+=<:>"全角カッコ系を追加(実験的)setmps+=:,:,:,:,:,:,:,:,:"マッチペアを検索let flgMps =0formpinsplit(&mps,',')let pairs =split(mp,':')ifa:after== pairs[0]||a:after== pairs[1]let flgMps =1breakendifendfor"マッチペアを戻すlet&mps= mps_save

    if flgMps ==0"マッチペアに該当しない場合は、同じ文字を設定する。lets:afterStart=a:afterlets:afterEnd=a:afterelse"マッチペアに該当した場合は、組合せを設定するlets:afterStart=pairs[0]lets:afterEnd=pairs[1]endifendf "}}}fu!s:getSorround(dotRep) abort "{{{"囲う対象を取得ifa:dotRep==v:true
        let what =b:whatelseredraw!
        echo s:cmdlet what =nr2char(getchar())letb:what= what
    endif"すでにビジュアルモードの場合、モードを終了する。(現状はnmapのみなのであり得ない想定)ifmode()==? "v"
        exec "normal! ".mode()endif"内側の範囲を取得
    exec "normal vi".what
    lets:iEnd=getcurpos()
    normal!olets:iStart=getcurpos()
    normal!v"外側の範囲を取得
    exec "normal va".what
    lets:aEnd=getcurpos()
    normal!olets:aStart=getcurpos()
    normal!v"範囲確認用"message clear"echom s:aStart[2]."-".s:iStart[2]."(".s:iStart[1].") , ".s:iEnd[2]."-".s:aEnd[2]."(".s:iEnd[1].")"endf "}}}fu!s:getMotion(dotRep) abort "{{{letl:count=""letl:where =""letl:what  =""ifa:dotRep==v:true
        "ドットリピート時は前回値を使用letl:count=b:countletl:where =b:whereletl:what  =b:whatelseredraw! 
        echo s:cmdlet wk =nr2char(getchar())while wk =~"[0-9]"letl:count.= wk
            redraw!
            echo s:cmd.l:countlet wk =nr2char(getchar())endwhileif wk =~"[ia]"letl:where = wk
            redraw!
            echo s:cmd.l:count.l:where
            letl:what  =nr2char(getchar())elseletl:what = wk
        endifredraw!
        echo s:cmd.l:count.l:where.l:what

        letb:count=l:countletb:where=l:where
        letb:what=l:what
    endif"内側の範囲を取得
    exec "normal v".l:count.l:where.l:what
    lets:iEnd=getcurpos()
    normal!olets:iStart=getcurpos()
    normal!vendf '}}}

nmap .:<C-U>call<SID>dotRepeat()<CR>fu!s:dotRepeat() "{{{ifexists("b:dotReady")&& b:dotReady!=0silent exec "call <SID>".b:dotRepeatFunc."(".v:true.")"redraw!else
        normal!.endifendf "}}}

nmap u:<C-U>call<SID>undoRepeat("u")<CR>
nmap U :<C-U>call<SID>undoRepeat("U")<CR>fu!s:undoRepeat(cmdUndo) "{{{ifexists("b:dotReady")&& b:dotReady!=0letb:dotReady=2endif
    exec "normal! ".a:cmdUndoendf "}}}fu!s:funcName(sfile) "{{{returnsubstitute(a:sfile,".*_","","")endf "}}}"互換性オプションを復元let&cpo=s:save_cpo"test"<a>'(hoge hoge )'</a>"<a>'(hoge hoge )'</a>"<a>'(hoge hoge )'</a>

ドットリピートをアンドゥ後にも有効にするためには、undoRepeat()関数がキモになります。
uまたはUが押されたときに、ドットリピート用のフラグを再設定してから、アンドゥするようにしてあります。

また、ドットリピート用の関数名をベタ書きにしていましたが、expand("<sfile>")を用いて取るように修正しました。

コメント文も徐々に充実してきましたね。

でも実は、まだまだ問題が残っています。
ysが多少まともに動くようになったことにより、「タグで囲めない。」という事にようやく気が付きました。

例えば、ysiwtとしたときに等は、tという文字そのもので囲まれてしまいます。まるで意味がありません。

同様に、cst<と入力した時点で、既存のタグが消えて、<>で囲まれてしまいます。
置換元のtの指定はうまくいっているのですが、置換後の文字列としてタグを入力することができないのです。

その4に続きます。

その4

変更後や、追加する囲い文字を入力するときに、<ではじまった場合は、>まで貯めて、編集するように修正しました。
<>で囲いたい場合は、>を入れればOK。

ついでに、行数の削減を少々行いました。

後は本家にあって、出来ていないことはヴィジュアルモードでSというものですが、リピートで繰り返しづらそうですし、あまり必要性を感じないので、ここまででいったん終了。

まだvimファイルを開いて、:so %して動かす形でしかありませんが、チョロチョロと名前などを変えてpluginディレクトリに放り込めば、もうこのままでも動くのではないでしょうか。

でもまぁ、それは追々。テキストオブジェクトの2文字目の%にも対応できたときに、まとめて体裁を仕上げようかと思います。

つまり実用していないので、本当にちゃんと実用に足るレベルで完成しているのかどうかはまだ謎なのです。
何せ「手習い」という記事なのでそれも止む無しなのですな。

omap_test5.vim
" vi: set et ts=4 sts=4 sw=4 fdm=marker :" sorround.vimの真似事。ysの動きも合わせたつもり。タイポ時はu頼み。scriptencoding utf-8"二重ロード避けifexists("g:loaded_omap_test5")finishendifletg:loaded_omap_test5=1"互換性オプションを退避して規定値に設定lets:save_cpo=&cposetcpo&vimauTextChanged,TextChangedI,TextChangedP,TextYankPost * call<SID>textChanged()fu!s:textChanged() "{{{ifexists("b:dotReady")"テキストが変更されたときに、-1する。letb:dotReady=(b:dotReady==0 ? 0:eval(b:dotReady-1))endifendf "}}}"nnoremapではなく noremap にしてしまうと、ddとかが遅延発動して気持ち悪くなるので注意。
nnoremap <silent>cs:<C-U>call<SID>cSorround(v:false)<CR>
nnoremap <silent>ds:<C-U>call<SID>dSorround(v:false)<CR>
nnoremap <silent> ys :<C-U>call<SID>ySorround(v:false)<CR>fu!s:cSorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="cs""囲い部分の範囲を取得calls:getSorround(a:dotRep)"ドットリピート時は再取得しないifa:dotRep==v:false
        "変更後の囲い文字を取得calls:getAfter()endif"囲い終わり箇所の置き換えcallsetpos('.',s:iEnd)| normal!lvcallsetpos('.',s:aEnd)
    exec "normal! c".b:afterEnd"囲い始め箇所の置き換えcallsetpos('.',s:aStart)| normal!vcallsetpos('.',s:iStart)| normal!h
    exec "normal! c".b:afterStart"自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2|redraw!endf "}}}fu!s:dSorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="ds""囲い部分の範囲を取得calls:getSorround(a:dotRep)"囲い終わり箇所を削除callsetpos('.',s:iEnd)| normal!lvcallsetpos('.',s:aEnd)| normal!d"囲い始め箇所を削除callsetpos('.',s:aStart)| normal!vcallsetpos('.',s:iStart)| normal! hd

    "自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2|redraw!
endfu "}}}fu!s:ySorround(dotRep) abort "{{{"ドットリピート用に関数名を保持letb:dotRepeatFunc=s:funcName(expand("<sfile>"))lets:cmd="ys""囲う部分のテキストオブジェクト部分を取得calls:getMotion(a:dotRep)"ドットリピート時は再取得しないifa:dotRep==v:false
        "追加する囲い文字を取得calls:getAfter()endif"囲いを追加callsetpos('.',s:iEnd)| exec "normal! a".b:afterEndcallsetpos('.',s:iStart)| exec "normal! i".b:afterStart"自身の終了時にイベントで-1されるため、2を設定letb:dotReady=2|redraw!endf "}}}fu!s:getAfter() abort "{{{redraw!| echo s:cmdlet wk =nr2char(getchar())if wk =='<'"タグの場合は、開始タグ・終了タグletl:tagStart =""while wk !='>'letl:tagStart .= wk
            redraw!| echo s:cmd.l:tagStart
            let wk =nr2char(getchar())endwhileletl:tagStart .= wk
        redraw!| echo s:cmd.l:tagStart

        letb:afterStart=l:tagStart
        letb:afterEnd=substitute(l:tagStart,'<','</','')else"タグ以外の場合は、対になる記号、または、同一記号let after = wk
        let mps_save =&mps"アングルブラケットをマッチペアに追加※「<」はタグ入力のトリガーなので、「>」setmps+=<:>"全角カッコ系を追加(実験的)setmps+=:,:,:,:,:,:,:,:,:"マッチペアを検索let flgMps =0formpinsplit(&mps,',')let pairs =split(mp,':')if after == pairs[0]|| after == pairs[1]let flgMps =1|breakendifendfor"マッチペアを戻すlet&mps= mps_save

        if flgMps ==0"マッチペアに該当しない場合は、同じ文字を設定する。letb:afterStart= after
            letb:afterEnd= after
        else"マッチペアに該当した場合は、組合せを設定するletb:afterStart=pairs[0]letb:afterEnd=pairs[1]endifendifendf "}}}fu!s:getSorround(dotRep) abort "{{{"囲う対象を取得ifa:dotRep==v:true
        "ドットリピート時は前回値を使用let what =b:whatelseredraw!| echo s:cmdlet what =nr2char(getchar())letb:what= what
        lets:cmd.= what
    endif"すでにビジュアルモードの場合、モードを終了する。(現状はnmapのみなのであり得ない想定)ifmode()==? "v"
        exec "normal! ".mode()endif"内側の範囲を取得
    exec "normal vi".what
    lets:iEnd=getcurpos()| normal!olets:iStart=getcurpos()| normal!v"外側の範囲を取得
    exec "normal va".what
    lets:aEnd=getcurpos()| normal!olets:aStart=getcurpos()| normal!v"範囲確認用"message clear"echom s:aStart[2]."-".s:iStart[2]."(".s:iStart[1].") , ".s:iEnd[2]."-".s:aEnd[2]."(".s:iEnd[1].")"endf "}}}fu!s:getMotion(dotRep) abort "{{{letl:count=""letl:where =""letl:what  ="""モーション文字列を取得ifa:dotRep==v:true
        "ドットリピート時は前回値を使用letl:count=b:countletl:where =b:whereletl:what  =b:whatelseredraw!| echo s:cmdlet wk =nr2char(getchar())while wk =~"[0-9]"letl:count.= wk
            redraw!| echo s:cmd.l:countlet wk =nr2char(getchar())endwhileif wk =~"[ia]"letl:where = wk
            redraw!| echo s:cmd.l:count.l:where
            letl:what  =nr2char(getchar())elseletl:what = wk
        endifredraw!| echo s:cmd.l:count.l:where.l:what

        letb:count=l:countletb:where=l:where
        letb:what=l:what

        lets:cmd.=l:count.l:where.l:what
    endif"モーションの内側の範囲を取得
    exec "normal v".l:count.l:where.l:what
    lets:iEnd=getcurpos()| normal!olets:iStart=getcurpos()| normal!vendf '}}}

nmap .:<C-U>call<SID>dotRepeat()<CR>fu!s:dotRepeat() "{{{ifexists("b:dotReady")&& b:dotReady!=0silent exec "call <SID>".b:dotRepeatFunc."(".v:true.")"|redraw!else
        normal!.endifendf "}}}

nmap u:<C-U>call<SID>undoRepeat("u")<CR>
nmap U :<C-U>call<SID>undoRepeat("U")<CR>fu!s:undoRepeat(cmdUndo) "{{{ifexists("b:dotReady")&& b:dotReady!=0letb:dotReady=2endif
    exec "normal! ".a:cmdUndoendf "}}}fu!s:funcName(sfile) "{{{returnsubstitute(a:sfile,".*_","","")endf "}}}"互換性オプションを復元let&cpo=s:save_cpo"test"<a>'(hoge hoge )'</a>"<a>'(hoge hoge )'</a>"<a>'(hoge hoge )'</a>

最後に

外部定義によって動作をカスタマイズする余地の欠片もない代物ですが、意外に簡単に実装できてしまいました。これは世の中にスクリプトが溢れかえるわけですね。

テキストオブジェクト2文字目の%の作成については、次の記事に分けようと思います。(まだ手を付けていない)


vim-pandoc-syntaxでurlを非表示にしたい

$
0
0

markdown/vimwiki/pandocのsyntaxハイライト

vimwikiのsyntaxでは、以下のようなリンクは

[説明](url)

このように表示されます(+concealオプションが有効な場合)

説明

これがあると、長いURLを持つドキュメントでの可読性がグンと上がります

例えば、blender data blocksの記事はmarkdown/vimwiki/pandocそれぞれのsyntaxで
以下のように見えます。

markdown, vimwiki, pandocのハイライトの違い

さて、これを普段使いのpandocsyntaxでも使いたいというのが今回の希望です。

結論: デフォルトの変数を設定しろ

もうこれが答えでした!

ドキュメントよもうね!!!!

letg:pandoc#syntax#conceal#urls =1

これだけでURLのconcealが有効になり、vimwikiと同じような見た目になります

設定後のpandoc

おまけ

ちなみに該当のコードは300-304行目にありました。

ifg:pandoc#syntax#conceal#urls ==1syn region pandocReferenceURL matchgroup=pandocOperator start=/\]\@1<=(/ end=/)/ keepend conceal
elsesyn region pandocReferenceURL matchgroup=pandocOperator start=/\]\@1<=(/ end=/)/ keepend
endif

vimで.phpファイルに書いたhtmlを自動でインデントする方法

$
0
0
" Better indent support for PHP by making it possible to indent HTML sections
" as well.
if exists("b:did_indent")
  finish
endif
" This script pulls in the default indent/php.vim with the :runtime command
" which could re-run this script recursively unless we catch that:
if exists('s:doing_indent_inits')
  finish
endif
let s:doing_indent_inits = 1
runtime! indent/html.vim
unlet b:did_indent
runtime! indent/php.vim
unlet s:doing_indent_inits
function! GetPhpHtmlIndent(lnum)
  if exists('*HtmlIndent')
    let html_ind = HtmlIndent()
  else
    let html_ind = HtmlIndentGet(a:lnum)
  endif
  let php_ind = GetPhpIndent()
  " priority one for php indent script
  if php_ind > -1
    return php_ind
  endif
  if html_ind > -1
    if getline(a:lnum) =~ "^<?" && (0< searchpair('<?', '', '?>', 'nWb')
          \ || 0 < searchpair('<?', '', '?>', 'nW'))
      return -1
    endif
    return html_ind
  endif
  return -1
endfunction
setlocal indentexpr=GetPhpHtmlIndent(v:lnum)
setlocal indentkeys+=<>>

これを~/.vim/indent/php.vimの中に書けばオッケー!

参考:
https://vim.fandom.com/wiki/Better_indent_support_for_php_with_html

WSL上のVimからクリップボードに保存するためのvimrc

$
0
0

問題提起

WSLデフォルトのターミナルでは、右クリックでコピー・ペーストを行う。ペーストについては概ね問題ない1のだが、Vimで編集中のテキストをコピーするとよく失敗する。例えば.vimrcset numberが記載されていると、行番号ごとコピーされてしまう。

# コピー前
defsample():pass# コピー後(失敗)
1defsample():2pass

Qiitaの記事を見ていると、VcXsrvを使えばヤンクした内容をそのままクリップボードに保存できるらしい。だが私は新しいソフトウェアをインストールしたくないので、vimrcの編集だけでどうにかする

解決策

.vimrcに以下を追記する。ビジュアルモードのyで、通常のヤンクと同時にクリップボードへのコピーも実行されるようになる。スクリプトの意味はコメントを見てほしい。

function MyClip()" 現在の位置とビジュアルモード開始位置を記録letl:dot_pos =getpos(".")letl:v_pos =getpos("v")" 現在の位置とビジュアルモード開始位置を比較し一方をstart、他方をendとするifl:dot_pos[1]<l:v_pos[1]||(l:dot_pos[1]==l:v_pos[1]&& l:dot_pos[2]<=l:v_pos[2])letl:start ="."letl:end ="v"elseletl:start ="v"letl:end ="."endif" start, endの行番号を記録letl:start_row =line(l:start)letl:end_row =line(l:end)" 先頭行・最終行から後処理で除く必要がある文字数を記録letl:ltrim =strchars(getline(l:start))-strchars(matchstr(getline(l:start),".*",col(l:start)-1))letl:rtrim =strchars(matchstr(getline(l:end),"^.*",col(l:end)-1))-1" 外部で実行するコマンドを文字列として作成return"y:\<c-u>silent".l:start_row .",".l:end_row ."!sed\<space>-E\<space>'1\<space>s/^.{".l:ltrim ."}//'\<space>|\<space>sed\<space>-E\<space>'$\<space>s/.{".l:rtrim ."}$//'\<space>|\<space>clip.exe\<cr>u"endfunction" 定義した関数をビジュアルモードの`y`にマッピング
vnoremap <expr>y MyClip()

コマンドを外部で実行するという考えを実現するにあたり、こちらの記事が大変参考になった。

動作確認

さっと書いたわりにちゃんと動いたが、問題点もあるので以下参照。動作確認した環境はUbuntu 20.04 LTS (WSL2)、Vimのバージョンは8.1。

  • Good
    • 日本語などのマルチバイト文字にも対応している。
  • Bad
    • 一瞬画面が点滅する程度だが、通常のヤンクより動作が遅いかも2
    • 通常のビジュアルモードを想定したスクリプトのため、<S-v>の行選択や<C-v>の矩形選択では動作しない。

最後に

Vimはいいぞ!


  1. Vim側で:set pasteを実行するのが手間だが、それさえ忘れなければ大丈夫。 

  2. 通常のヤンクが遅くならないように、今回実装した機能はy以外のキーにマッピングした方が快適かもしれない。Vim内で完結するコピペには通常のヤンク(y)を使い、外部にコピペするときだけ今回実装した機能を使うイメージ。 

vim で C/C++ 系ファイルでインデントさせないようにする

$
0
0

背景

set nocindentでインデント無効にできるはずであるが,
Ubuntu 20.04 のころから?(vim 8.1),
set nocindent~/.vimrcに記述しても C/C++ ファイルでインデントしたまになってしまう.
(cindentがなぜか有効になる)

解決方法

Ubuntu 20.04 + vim 8.1(標準で入っているもの) とします.

"set nocindent" doesn't work in ~/.vimrc [solved]
https://forums.gentoo.org/viewtopic-p-3551447.html?sid=048d6eb2117269b64d39104c49f0da25

とりあえず

autocmd BufEnter * set noautoindent nocindent  

~/.vimrcに書けば解決します.

詳細

Vim の自動インデント機能について
https://machakann.hatenablog.com/entry/2015/08/15/132247

を参考に調べればどこで cindentが有効になっているかわかる... かも?

vim vimのmap機能を使って、C言語コードランナーを実装しよう。

$
0
0

以下を.vimrcに設定する。

map <F5> :!clear && gcc % && ./a.out<CR>

Cファイルを編集中にF5を押すとコードが実行されます。

Viewing all 5722 articles
Browse latest View live