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

n番目に新しいファイルをvimで開く

$
0
0

目的

複数のlogファイルの最新をvimで開く方法を書きました。

もっとも新しいファイルをviで開くbashのalias

欲が出てn番目の新しいファイルも開きたいときはどうしましょうか?

参考

bashで引数つきのエイリアスを設定する

考えかた

引数を渡したいので関数で書きます。

$ tail ~/.bashrc
~~
# 引数の番号の最新をvimで開くfunction vl(){[$# -eq 0 ]&&command vim $(ls-t | sed-n 1p)[$# -ne 0 ]&&command vim $(ls-t | sed-n$1p)}$ vl 3 #<- これで3番目に新しいファイルが開きます。

説明

引数の数($#)で処理を変えます。引数がなければsedで1行目を表示します。引数があればsedで1番目の引数の中身を展開します。vlという関数を定義するのですが、ls -tで時間が新しい順に表示させて、sed$1番目のファイルを表示します。それをvimで受け取ります。参考によるとcommandがないと

内部で用いられているcommandは関数やエイリアス以外のコマンドを優先して実行するためのコマンドです。

だそうです。
様々な環境で動くかなんていうのは調べていないですが、ContOS7ではOKでした。どなたかのお役に立てると嬉しいです。


vimのカラースキームを変更する方法

$
0
0

Linuxなどのコンソールでファイルをvimで開いたときに色が見にくい時があります。

そんな時に色を変更する方法をメモがてら共有します。

vimを開いてから変更する方法

①コロン(:)をうちコマンドモードにし「colorscheme」と入力します。colorと入力してtabキーを入力すれば保管されます。
②tabキーを押して使用したいカラースキームを選択します。
例えば「elflord」というカラースキームを変更する際には以下のようになります。

:colorscheme elflord

恒常的にカラースキームを設定する方法

vimを開くたびにカラースキーム設定をするのは面倒です。

そこでデフォルトのカラースキームを設定します。
設定を書き込むのは「vimrc」というファイルです。

vimrcは各ユーザーのホームディレクトリ配下に隠しファイルとして用意されています。

なので以下のように開けます。

vim ~/.vimrc

そして以下のように書き込み保存します。(カラースキームとしてelfordを使用した場合)

colorscheme elflord

vimrcはvimを開いたときに設定として読み込まれますのであとはvimを開けばカラースキームは自動で置き換わってくれます。

Denite + Defx で複数プロジェクトを渡り歩く

$
0
0

目的

複数のプロジェクトで作業をしないといけない場合、プロジェクトを切り替えながら渡り歩くのが結構めんどくさかったりします。
そういった場合にDenitedenite#custom#varを使うとプロジェクト間の行き来が少し楽になります。

環境

NVIM v0.5.0-94b7ff730
DeniteDefxが使えることが前提です。

詳細

:help denite#custom#var

設定

以下のように設定すると<Leader>pでプロジェクトの選択肢が表示され、Enterでプロジェクトルートが開きます。

lets:menus={}lets:menus.projects ={'description':'switch projects'}lets:menus.projects.command_candidates =[      \['project A','Defx ~/path/to/projectARoot'],      \['project B','Defx ~/path/to/projectBRoot'],      \['project C','Defx ~/path/to/projectCRoot'],      \]call denite#custom#var('menu','menus',s:menus)

nnoremap <silent><Leader>p:<C-u>Denite menu:projects<CR>

Defx以外のコマンドでも、command_candidatesに追加すれば選択式で使えるので結構便利そう。

ブラウザをやめてVimでGitHubを使う

$
0
0

はじめに

こちらのクロス投稿です。

最近仕事でGitHubを使っていますが、ターミナルとブラウザの行き来が面倒になってきたので
Vim上でGitHubを操作できるプラグインgh.vim1をつくりました。

ようやくまともに動くようになったので、
プラグインの紹介と実装について解説していきます。

デモ

こんな感じで使います。

機能一覧

  • issue 一覧、作成、更新、close
  • repository 一覧、作成、削除(デフォルトOFF)
  • pull request 一覧、差分

使い方

詳細はREADMEやhelpを読んで頂ければと思います。ここでは大まかな使い方を解説します。

gh.vimは通常のプラグインのようなコマンドを用意してません。
代わりにgh://から始まるバッファを開くことでGitHubに対する様々な操作を可能にします。

例えば:new gh://:owner/:repo/issuesでバッファを開くとissue一覧が表示されます。
:ownerはユーザ名もしくはorg名、:repoはリポジトリ名です。

現時点で使用可能なバッファは次になります。
ちょっとわかりづらいので補足します。
gh://user/reposは自身がアクセスprivateやorgnizationのリポジトリ一覧
gh://:owner/reposは他のユーザの公開リポジトリ一覧
を確認できます。

bufferdescription
gh://:owner/:repo/issues[?state=open&..]get issue list
gh://:owner/:repo/issues/:numberedit issue
gh://:owner/:repo/issues/newcreate issue
gh://:owner/reposget repository list
gh://user/reposget authenticated user repository list
gh://user/repos/newcreate repository
gh://:owner/:repo/readmeget repository readme
gh://:owner/:repo/pulls[?state=open&...]get pull request list
gh://:owner/:repo/pulls/:number/diffshow pull request list diff

さて、お気づきの方がいるかも知れないんですが、HTTPのURIと似た形になっています(http -> ghに変わったくらい)
普段エンジニアの我々からすれば馴染みがありますね。

このようなIFにしたのは
これまでに無い形のプラグインをつくってみたい(1つもコマンドを提供しない)
というのが一番です。

現時点で特にお気に入りな機能としては

  • issue 作成と更新
  • pull request の差分表示

です。どちらも業務で実際結構使用するので、それがVimできるようになったのはコストが低下して良きです。

仕組み

コマンドを提供しないで、どうやってバッファを開いたときに処理させているかについて解説します。

VimにはBufReadCmdというautocmdがあります。こちらはパターンにマッチしたバッファ(名)が読み込まれるときに発火して指定した関数を実行します。

この機能を利用して、gh://*から始まるバッファが読み込まれるときに任意の関数を処理させています。
実際の定義は次になっています。(一部抜粋)

auBufReadCmd gh://*/repos call gh#repos#list()auBufReadCmd gh://*/repos\?* call gh#repos#list()auBufReadCmd gh://*/*/issues call gh#issues#list()auBufReadCmd gh://*/*/issues\?* call gh#issues#list()auBufReadCmd gh://*/*/issues/[0-9]* call gh#issues#issue()

バッファ名はVimの正規表現が使えます。
そして関数内でバッファ名から必要な:ownerと:repo情報を取得してcurlでGitHubのAPIを叩いています。

実は、これは以前thincaさんに教えて頂いていました。
更にlambdaalisueさんのgina.vimというプラグインも同じ仕組みを使っていました。

この仕組みを使うメリットとしては

  • バッファを開いた時の処理と開く前の処理を分断できる
  • 同じ処理をさせたければバッファを開くだけで良くなる

なので、プラグイン次第では実装が結構楽になります。

今後

当初VimでGitHubのPRをレビュー(コメント、approve、change requestなど)したいというのが目的でした。
個人的な目的はまだ達成していないので、今後はそこを達成したら更にUI周りもリッチにして行きたいと感がています。

ひとまずRloadmapを片付けてからってところですね。

雑感

仕組み自体はシンプルですが、Vimのバッファ操作周りが相変わらず面倒なので色々と大変でした。
みなさんのフィードバックをお待ちしています。


  1. 現時点ではNeovimでは動作しない、対応したい気持ちはある 

TinyGo + `VSCode or Vim (もしくはそれ以外の LSP 対応エディタ)` で gopls 連携する方法

$
0
0

TinyGo 0.15 がリリースされて、 gopls との連携が簡単になりました。
逆に言うと今までは 設定がそれなりに面倒でした。

このページでは、 TinyGo 0.15 以降での gopls 連携の方法を VSCodeおよび Vim (もしくはそれ以外の LSP 対応エディタ)に対して記載します。

注意)
以降、 wioterminalというターゲットを例として記載します。
適宜、使いたいターゲットに読み替えてください。

VSCode

まずは、 VSCode の Extensions > TinyGo 0.2.0をインストールします。

Screenshot from 2020-09-23 21-28-47.png

次に tinygo のソースコード (*.go) を開いている状態で Command palette > TinyGo targetsを実行します。

Screenshot from 2020-09-23 21-22-51.png

pick a target...と表示されるので wioterminalを選択します。

Screenshot from 2020-09-23 21-21-01.png

Reload を促されるので Reload します。

Screenshot from 2020-09-23 21-21-46.png

この時点で machine.LEDの定義が表示されていれば gols 連携は OK です。

Screenshot from 2020-09-23 21-23-24.png

ターゲットを切り替えたい場合は、 Command palette > TinyGo targetsを再度実行してください。

Vim (もしくはそれ以外の LSP 対応エディタ)

まずは tinygo-editをインストールします。
以下から実行体を DL し PATH の通った場所に置いてください。

もしくは、以下のコマンドでもインストールすることが出来ます。

$ go get github.com/sago35/tinygo-edit

その後、以下をソースコードのあるディレクトリで実行します。
エディタに合わせて少し設定が必要です。

# Vim
$ tinygo-edit --target xiao --editor vim --wait

# gVim
$ tinygo-edit --target xiao --editor gvim

# VSCode (VSCode は上記の TinyGo extensions を使ったほうが良い)
$ tinygo-edit --target xiao --editor code

この時点で machine.LEDの定義が表示されていれば gopls 連携は」 OK です。

Screenshot from 2020-09-23 21-27-16.png

なお、この方法で開いた vim から立ち上げた terminal では tinygo buildtinygo flashが出来ません。
環境変数 GOROOT が設定されているためです。

Screenshot from 2020-09-23 21-32-31.png

以下のようにして GOROOT の設定を削除すると tinygo build等を実施出来るようになります。

# windows / cmd.exe の場合
$ set GOROOT=

# bash の場合
$ unset GOROOT

Screenshot from 2020-09-23 21-35-29.png

ターゲットを切り替えたい場合は、 tinygo-editを再度実行してください。

Link

Vim Tips

【備忘録】読み取り専用ファイルの強制上書き

$
0
0

本来であればsudo権限で開くべきファイル(Nginxの設定ファイルとか)を普通に開いてしまって、:wqコマンドで保存できない…という時の対処法です。たまにやっちゃうのでメモがてら。

:w !sudo tee %

▲まずこれを実行。

:wで「内容の保存」、sudo teeが「ファイルへの書き込み」、%が「現在開いているファイル」を表しています。

:q!

その後、本来なら保存せずに終了する際に使用する上記コマンドでviを終了。

MacにてVisual Studio Codeを使ったC#構築手順

$
0
0

前の投稿にて、Vimエディタを使ってJava環境を構築しようとして、挫折した。
そのため、プログラミングでvimエディタは合わないのだろうと判断し、違うエディタを選ぶことにした。
IDEにしなかった理由は、少しで動作を軽くしたかったから(だからvimを選びたかった)。

んで、たかがエディタを使うだけであるにも関わらず、コンパイルから実行まで4〜5時間掛かってしまったため、備忘録として残すことにする。

インストールなど。

さすがに省略する。

Visual Studio for Macから取得すれば、そのまま使えたはず。
そして、CSharpの場合は、.Net Core 3.1 SDK 以降が必要になる。

起動

Visual Studio Code.appを叩けばいいだけなので、気にすることはない。

1.VisualStudioCode.jpg

ワークスペースフォルダを追加

IDEであれば、プロジェクト作成の表現を使うことだろう。

2.ワークスペースディレクトリ.jpg

"ようこそ"タブがない場合は、、、どうする?

3.ディレクトリ作成.jpg

ワークスペースに作成したフォルダが(作成したため)表示される。

脱線した話。

個人的には、フォルダーの長音記号は邪道だと思っている。そもそも命名規則違反だし、、、

その言葉が 3 音以上の場合には,語尾に長音符号を付けない。

別途参照:3音ルール-語尾の長音記号の使い分け-について。

Microsoft社が長音記号を付けることにしたのがな・・・。
今から2008年の出来事か。思った以上に昔だった。

ドットネット用の下地作成

".NET Core コンソール アプリ"プロジェクトを作成するため、コマンドプロンプト(Control+`記号で表示可能)に、 dotnet new consoleを打ち込む。

4.どっとねっと.jpg

実行したことで、必要なファイルが自動生成される。

C#をVisual Studio Codeに導入

上記のコマンドでC#ファイル(*.cs)も自動生成されているため、これを開く。

5.csファイル.jpg

そうした場合、C#用の拡張有無を聞かれるため、 "Yes"ボタンを押下する。
※一定時間後に、この確認ダイアログは消える。

試しに動かす

Visual Studio Codeに、C#用ソフトウェアが導入されたことを"アプリを拡張する"というそうだ。
これにより、C#ファイルをコンパイル及び実行できる。

ここでもまた、コマンドプロンプト上で、 dotnet runを実行する。

6.どっとねっとを走らせる.jpg

プロンプトに"Hello World!"が表示された。

デバッグ準備

冒頭で作成したqiitaSampleディレクトリ配下に、隠しディレクトリとして.vscodeがある。
この中に、launch.jsonファイルがあり、この中身を書き換えることで、デバッグ実行が可能になる。

7.虫.jpg

変更前:"console": "internalConsole",
変更後:"console": "integratedTerminal",

デバッグ

今回は、プロンプトからではなく、左側にある三角形と虫の絵が付いたボタンを押すことで、デバッグ実行になる。

8.虫潰し.jpg

プルダウンメニューには、".NET Core Launch (console)[ワークスペースフォルダ名(?)]"が選択されていることを確認すること

今回の場合のワークスペースフォルダ名は、"qiitaSample"になる。

デバッグ途中

今回は10行目にブレークポイントを設置した。

9.壊す.jpg

ブレークポイントは、行番号の左側をクリックすることで設置できる。

ステップ実行など。

あとは各々好きにデバッグをすれば良い。

10.虫とのステップ.jpg

処理を最後まで走らせたのが以下画像になる。

11.bash.jpg

以上。

一応手順をまとめたが、本当にこんな手順に躓いたとは思えない。
そのため、環境をいじっていたときに、正しい環境になってしまい、躓いた箇所を再現できずに、スムースに終わってしまったと思われる。
無事に環境構築が出来たのはいいのだが、ちょっと腑に落ちない。
無念ではあるが、結果オーライと言うことでよかった。

■公式ページ
チュートリアル: Visual Studio Code を使用して .NET Core コンソール アプリケーションを作成する
チュートリアル: Visual Studio Code を使用して .NET Core コンソール アプリケーションをデバッグする


Vim patchダイジェスト [2020/08]

$
0
0

Vimのリリースされたpatchの説明です。
(8.2.13348.2.1551)
新機能、大幅な仕様変更には、:four_leaf_clover:が付いています。
Vim9 scriptの実装と不具合修正も頻繁におこなわれています。
※今月もVim9 scriptの不具合修正が結構多かったので、その辺り端折ってます。

  • 8.2.1549: 'esckeys'がオフの場合は、bracketed paste (:h xterm-bracketed-paste)とmodifyOtherKeysを一時的に無効にするようにしました。
  • runtimeファイル更新: Todo更新。helpファイル更新。他。
  • 8.2.1544: :four_leaf_clover:gettext()を追加しました。可能であれば、引数{text}を翻訳します。
  • 8.2.1536: :four_leaf_clover:charclass()を追加しました。引数{string}内の最初の文字の文字クラスを返します。(関連patch: 8.2.1540, 8.2.1548)
  • 8.2.1535: :four_leaf_clover:setcellwidths()を追加しました。文字の範囲のセル幅の上書きを指定します。これは、画面セルでカウントされる文字幅をVimに伝えます。'ambiwidth'を上書きします。(関連patch: 8.2.1537)
  • 8.2.1517: :four_leaf_clover:strpart()に第4引数{chars}を追加しました。TRUE指定時は{start}および{len}がバイト単位ではなく文字単位になります。
  • 8.2.1482: Vim9: lambda式をネストして使用するとクラッシュする件を修正しました。
  • 8.2.1472: :argdelがhelpの記載通りに動作せずエラーになる件を修正しました。
  • runtimeファイル更新: Todo更新。helpファイル更新。他。
  • 8.2.1461: :four_leaf_clover:Vim9: 文字列の要素(:h expr-[])や部分文字列(:h expr-[:])のインデックスを文字単位に変更しました。(※旧来のVim scriptはバイト単位) (関連patch: 8.2.1462)
  • 8.2.1422: :alien:macOS用のGUI実装を削除しました。(※有志による別プロジェクト macvim-devで開発がおこなわれています) (関連patch: 8.2.1442)
  • 8.2.1413: :four_leaf_clover::tabclose, :tabonly, :tabnextおよび :tabmoveのオプションに #を指定できるようにしました。最後にアクセスしたタブページに対してコマンドを実行します。(関連patch: 8.2.1401)
  • 8.2.1401: :four_leaf_clover:g<Tab>および<C-Tab>(GUIのみ)を追加しました。最後にアクセスしたタブページへジャンプします。また、tabpagenr()の引数{arg}に'#'を指定すると最後にアクセスしたタブページ番号を返すようにしました。(関連patch: 8.2.1413)
  • runtimeファイル更新: Todo更新。helpファイル更新。他。
  • 8.2.1380: :four_leaf_clover:getreg()の第3引数{list}がTRUEの時は文字列のリストを返すようにしました。
  • 8.2.1353: 端末ウィンドウにダブルワイド文字を描画した時にクラッシュすることがある件を修正しました。
  • 8.2.1347: :four_leaf_clover:expand('<SID>')でscript IDが簡単に取得できるようになりました。
  • 8.2.1343: 同名のローカル関数が存在する場合、g:を使用したグローバル関数が見つからない件を修正しました。
  • 8.2.1342: :four_leaf_clover:Vim9: Exコマンド:tの使用を禁止しました。(関連patch: 8.2.1308)

凡例

表記意味
:four_leaf_clover:新機能、大幅な仕様変更
:alien:Vim開発者向けの追加、変更
'hoge'オプション (:h options参照)
:hogeExコマンド (:h :index参照)
hoge()組み込み関数 (:h functions参照)
v:hogeVim定義済変数 (:h v:参照)
+hogefeature (:h +feature-list参照)

方針

こちらを参照。

vimでautocmdを使ってテンプレートを自動で読み込ませる

$
0
0

PHPファイルを新規で書く時に、いちいちhtmlを打つのがめんどくさかったので
.vimrcを編集して、autocmdを使ってhtmlを自動で読み込むようにしました。

目次

やり方
解説
ファイルパターン
おすすめのautocmd

やり方

まずはテンプレートファイルを設定しましょう
例えば~/.vim/skelton/php.txtに以下のように設定します

~/.vim/skelton/php.txt
<?php?><!DOCTYPE html><htmllang="ja"><head><metacharset="utf-8"><title></title></head><body></body></html>

その後にユーザー設定ファイル~/.vimrcもしくはグローバル設定ファイル/etc/vimrc
このように記述してください

~/.vimrc
autocmd BufNewFile[{blade}].php 0r $HOME/.vim/skelton/php.txt

このようにすることでvimを使えば
phpを記述したい場合はoを打てば改行して入力モードに入るし
bodyを記述したい場合は9jで9行下がってoだし
titleを設定したい場合は、6jで6行下がって、
oで入力し、ctr+cで入力モードを抜けて、Jでタイトルタグを横並びにすれば良い
これで煩わしい典型文を書かなくて済むし、何より時短になりますね

解説

軽く解説をすると、BufNewFileで新しいファイルを読み込んだ時って意味です
0r filepathでファイルパスのスケルトンファイルを読み込むようになってます

ファイルパスを適切に設定することでbladeという文字列が入らないphpファイルには
先ほどのテンプレートが読み込まれることになります

ここでbladeという文字列が入らないようにしているのはlaravelviewファイルはcakephpと違って
拡張子が.blade.phpとなっているからです

laravelファイルでは@extends('layouts.app')みたいに読み込むので
htmlを自動で読み込ませた場合は消す必要があるのでかえって面倒です。

laravelを使用しない場合は*.phpに読み替えて記述してください!

もちろんこれは複数設定できるので同じようにしてpythongoなどでも分けることができます

ファイルパターン

記号説明
*あらゆる文字の列にマッチ。注意: パス区切り文字も含まれる。
?あらゆる1文字にマッチ
\?'?' にマッチ
.'.' にマッチ
~'~' にマッチ
,パターンを分割する
\,',' にマッチ
{ }pattern の ( ) と同様
,('{' '}' の内側では) pattern の \
}リテラルの }
{リテラルの {
\{n,m}pattern の {n,m} と同様
\pattern で使われるものと同様の特別な意味を持つ
[ch]'c' または 'h' にマッチ
[^ch]'c' と 'h' 以外の文字にマッチ

おすすめのautocmd

autocmdを使えば
特定のファイルのタブスペースを変更させたり、特定のファイルを開いた時に関数にカーソルを持って行ったりできるので、是非マスターしてください

便利だなと思うのは以前開いていた位置にカーソルを戻してくれる設定です
これも同様にvim設定ファイルに記述してください

~/.vimrc
function!s:RestoreCursorPosition()if line("'\"")<= line("$")
    normal!g`"
    return1endifendfunction" ファイルを開いた時に、以前のカーソル位置を復元する
augroup vimrc_restore_cursor_position
  autocmd!
  autocmd BufWinEnter * calls:RestoreCursorPostion()
augroup END

公式ドキュメント
https://vim-jp.org/vimdoc-ja/autocmd.html
https://vim-jp.org/vimdoc-en/autocmd.html
参考
http://vim.wikia.com/wiki/Restore_cursor_to_file_position_in_previous_editing_session

vim-anywhereをmacvim-kaoriyaと共存させる

$
0
0

tl;dr

ln-s /Applications/MacVim.app/Contents/bin/mvim /usr/local/bin/mvim
curl -fsSL https://raw.github.com/cknadler/vim-anywhere/master/install | bash

echo"控えめに言って最&高"

Vimをどこでも使わせてくれ

Vimが,最高のテキストエディタの一つであることはよく知られている.
ひとたびVimの操作に慣れてしまえば,なぜ世の中のソフトウェアがVimライクに操作できないのか,全く意味がわからなくなるだろう.

そんな要望から作られたのがcknadler/vim-anywhereである.
どこからでも,ショートカットひとつで普段遣いのVimが立ち上がり,入力した内容はクリップボードにプレーンテキストとしてコピーされるので,実質的にはVimでテキスト入力をしたのと同じ体験が得られる.
いわゆる,最☆高というやつである.

日本語ユーザとしては,香り屋さんのVimも欠かせない

Vimは,基本的にはアルファベットの入力を想定して作られたテキストエディタではあるが,日本語環境もよく整備されている.
日本語入力環境の改善に多大なる貢献をし,またVimを日本に広めた双璧の一人でもあるKoRoNさんの香り屋パッチは,日本語をVimで取り扱う上で必須のものになっている.
香り屋さんのパッチがあらかじめ当てられたVimとしては,Windowsではvim-kaoriyaが,Macではmacvim-kaoriyaが,それぞれ配布されている.

さて,vim-anywhereはMacにインストールできるが,ぜひともmacvim-kaoriyaを使いたいものである.
しかし,vim-anywhereのインストール手順では,Homebrewを使ってmacvimをインストールせよ,と書いてある.
Homebrew上のmacvimは,当然香り屋パッチが当てられてないので,このままでは折角のvim-anywhereが日本語ユーザにとってイマイチ使いづらいものになってしまう.

vim-anywhereはシンプルにmvimにパスが通っているか確認しているだけ

ということでvim-anywhereで立ち上がるgVimを,普段遣いのmacvim-kaoriyaに設定したいわけだが,どうもvim-anywhereは,インストール前にmvimにパスが通っているかを確認しているらしい.
インストール後は,そのmvimのパスを呼び出してきているだけなので,ここを騙くらかしてやれば,任意のgVim環境でvim-anywhereを実行できるということになるだろう.

よって,単純にパスの通ったディレクトリに,macvim-kaoriyaのプログラムをシンボリックリンクする.

ln-s /Applications/MacVim.app/Contents/bin/mvim /usr/local/bin/mvim

あとは,vim-anywhereに記載の手順に従って

curl -fsSL https://raw.github.com/cknadler/vim-anywhere/master/install | bash

を実行すれば終わりである.
あとは,vim-anywhereの使い方をよく読むか,rcmdnk's blog/vim-anywhere: Mac/LinuxでどこでもVimを立ち上げてテキスト入力するを読んで設定する.

終わりに

Vimを,テキスト入力のインターフェースとして利用するというのは,とても良い発想だといえる.
vim-anywhereで呼び出されたgVimは,当然のように普段遣いしているvimなので,シンタックスハイライトや補完,スニペットの利用が可能である.
ここで作成したテキストは,プレーンテキストの形でクリップボードへ移されるので,メールにそのままペーストしてもなんの問題もない.
結局,我々が使うソフトウェアの多くは,入力されたテキストを最終的に整形して処理するだけのものに過ぎない.
テキスト入力環境そのものは,より効率的なソフトウェアと連携させるという考え方が最善なのではないだろうか.

脱トラックパッド

$
0
0

脱トラックパッド計画

普段の業務ではvimを使うこともありトラックパッドをあまり触りたくなくなってきたのでキーボードのショートカットキーを覚えていこうと決意。
現在の設定のメモ。これから追加していきます。

普段

キー動作
⌘+h一文字削除

chrome(vimumあり)

キー動作
⌘+l検索窓に移動
d下に50%スクロール
u上に50%スクロール
H前のタブに移動
L次のタブに移動
h戻る
j進む

Deniteでctags検索のSourceを作ってみる

$
0
0

Deniteに移行した時点から、ctags検索に不満があり
https://qiita.com/hisawa/items/3498951e84eac77ac890

fzfなどを使ってみましたが、nvimに移行してからctags検索だけfloatingではないのが気になってました。
https://qiita.com/hisawa/items/fc5300a526cb926aef08

探してもDeniteでctagsを検索する方法がみつからなかったので
Deniteのsourceを自作して、ctags検索ができるようにしました。
https://github.com/hsawaji/denite-ctags

DeniteのSourceを初めて作ったので、メモを残しておきます。

準備

まずはDeniteのhelpをみます。

:help denite-create-source

rplugin/python3/denite/source/*/.py.

にファイルを置くと、Sourceとして読み込まれると書いてあります。
すでに実装されている他のSourceを参考に、最低限__init__gather_candidatesがあれば動きそうなのがわかりました。

ここで簡単に動きを検証してみました。

test.py
from.baseimportBasefromdeniteimportutilclassSource(Base):def__init__(self,vim:util.Nvim)->None:super().__init__(vim)self.name='test'self.kind='file'defgather_candidates(self,context:util.UserContext)->util.Candidates:return[{'word':'hoge'},{'word':'fuga'}]
:Denite test
=> hoge
   fuga

gather_candidatesで返した配列が表示されるようになりました。
これで、後はctagsの候補を返してやれば完了するはず。。

ctagsの候補を表示する

vimにはtaglistというタグの検索結果を返してくれるやつがあるのでそれを使います。

:help taglist

既存のtag.pyをみると

  • __init__vimをインスタンス変数に入れる
  • self.vim.call('command', '引数')で呼び出す

とすれば行けそうです。

今回はtaglistなので

self.vim.call('taglist', 'word')

を追加しました。
ここまでで、こんなソースになります。

from.baseimportBasefromdeniteimportutilclassSource(Base):def__init__(self,vim:util.Nvim)->None:super().__init__(vim)self.name='test'self.kind='file'self.vim=vimdefgather_candidates(self,context:util.UserContext)->util.Candidates:taglist=self.vim.call('taglist',context['args'][0])return[{'word':t['cmd']}fortintaglist]

下記のようにしてhogehogeを検索します。

:Denite test:hogehoge

候補先に飛ぶ

Deniteのhelpを見ると

:help denite-kind-file

action__pathaction__patternがあれば目的地に飛べる感じがします。

それらを追加したソースがこちら。
taglist['cmd']には正規表現のように/^$/が入っていたのでre.subで削除しています。

importrefrom.baseimportBasefromdeniteimportutilclassSource(Base):def__init__(self,vim:util.Nvim)->None:super().__init__(vim)self.name='test'self.kind='file'self.vim=vimdefgather_candidates(self,context:util.UserContext)->util.Candidates:taglist=self.vim.call('taglist',context['args'][0])return[{'word':t['cmd'],'action__path':t['filename'],'action__pattern':re.sub('\/\^|\$\/','',t['cmd'])}fortintaglist]

ここまでほとんど完成です。
以上のソースの見た目などを調整したものがdenite-ctagsになります。

まとめ

Deniteのsourceを初めて作りましたが、簡単に作れることがわかりました。
ドキュメントや既存ソースがわかりやすかったので、それを参考に作ると良いと思います。

[Vim]基本操作負担軽減のための最低限Leader設定

$
0
0

はじめに

  • 普段利用しているエディタとは別に、日または月に数回程度vimを利用することがあります。
  • そのため、以下のような状態でも問題ありませんでした。
    • 利用は基本操作(移動・保存・終了)のみ。
    • そのため、数行あるいは空白の.vimrc
    • 複雑な操作は手入力かコピペ。
  • 今回は、そのような基本操作の更なる指負担軽減を図るため、VimのLeader機能の設定を記載します。

結果

  • 結果の.vimrcの記述を示します。
  • 以下のような設定で、基本操作(移動・保存・終了)が「Space + 各種キー」でも利用できるようになります。
" leaderをスペースへ設定let mapleader ="\<Space>"" 「Spaceキー + 各種キー」のようなキー操作マッピング
inoremap <Leader>jj <Esc>                         " ESCキー 
nnoremap <Leader>w:w<CR>                         " 保存
nnoremap <Leader>q:q<CR>                         " 終了
noremap <Leader>a myggVG$                         " 全選択(ノーマル)
inoremap <Leader>a<Esc>myggVG$                   " 全選択(インサート)
nnoremap <silent><Leader>vr :new~/.vimrc<CR>    " .vimrcを開く
nnoremap <silent><Leader>r:source ~/.vimrc<CR>  " .vimrcの読み込み
noremap <Leader><Leader><C-w>w                   " windowの移動
map <leader>n:call RenameFile()<cr>              " 編集中ファイルのリネーム

" リネーム関数定義function! RenameCurrentFile()letold= expand('%')letnew= input('新規ファイル名: ',old,'file')ifnew!=''&& new!=old
    exec ':saveas '.new
    exec ':silent !rm '.oldredraw!endifendfunction

内容

Leaderとは

  • Leaderとは、複数キー操作の割り当てが可能な特別な文字列および設定のことです。
  • つまり、Vim上で独自のキーマッピング(ショートカット)を作成できます。

設定

  • 設定の際には、以下を意識しました。

    • spaceキーを起点に。
      • 「,」等、様々なキーを試して自分に最適なものを選択。
    • 高頻度のみの設定
      • 利用の上で頻度の高い操作を洗い出す。
    • 必要以上には設定しない
      • 数を増やしすぎると、キーの衝突が起きるため避ける。
      • 短い標準操作は基本設定しない。
  • 上記の結果で設定した主な内容は以下。(※基本操作に焦点を当てて設定)

    • spaceキー + jj : ESCキー
    • spaceキー + w : 保存
    • spaceキー + q : 終了
    • spaceキー + a : 全選択
    • spaceキー + n : 編集中ファイルのリネーム

参考

テスト自動化ツール Gauge 用の coc extension coc-gauge を作った

$
0
0

久々の投稿です。

お仕事で e2e test を作ることになりまして、いろいろとツールを漁っていたところ、Gauge というツールに出会いました。なにやらテストを Markdown で書くことができて、仕様書のように人間が読める形でテストを書けるとのこと。なんだか Cucumber っぽい(使ったことないですけど)。

Gauge の Script(=Markdown) を書くための VSCode Pluginが公式で公開されており、普通はコレを使って開発していくのですが、やはり Vimmer としては Vim で書きたい、実行したい、デバッグしたい。というか VSCodeVim が重くて buggy でストレスたまる。

ということで coc extension coc-gaugeを作りました。本記事はその紹介になります。

Gauge とは

公式より

Gauge is a light-weight cross-platform test automation tool with the ability to author test cases in the business language.

e2e test、というよりも受入テスト用のツールと言った方がいいでしょうか。テストを Markdown 形式で書くことで、人間が読めるシナリオとしてテストを書き、かつそれぞれの操作ステップの実装コードを再利用可能とするためのツールです。実装コードは JavaScript, Jave, Go, C#, Python, Ruby で書くことが可能です。

Taikoというブラウザオートメーションツールとセットになっており、e2e テストを主眼としていますが、ブラウザテストに限らず使えます。また、Taiko 以外のブラウザ自動化ツール、例えば Selenium や Playwright などを使うことも可能です。

ThoughtWorks がプロジェクトをサポートしていましたが、2020年一杯でそれが終了するとのこと。今後のプロジェクトの進め方について、GitHub に Issueが挙がっています。

coc-gauge

https://github.com/navel3/coc-gauge

Gauge の Spec ファイルで入力補完を得るだけならば、coc config に language server の設定を書くだけでもできるんですが、テストの実行、デバッグができなければ VSCode の代わりにはならないということで、以下の機能を実装しました。

  • 編集中のテストの実行・デバッグ
  • Step の Rename (coc の組み込み機能だとうまく動かないので、特別に作成)
  • Step の参照箇所の一覧表示

詳しくは Readme を参照ください。

Install

npm で公開しているので、vim/nvim 上で次のコマンドを実行してください。

:CocInstall coc-gauge

キーマップ

デフォルトではキーマップは設定していないため、に従って、次のようにキーマップしてください。

nmap <leader>rn <Plug>(coc-gauge-rename-step)

Debug

Debug には vimspectorが必要なので、インストールしておいてください。

Gauge のプロジェクトルートに .vimspector.json の名で次の設定ファイルを配置してください。

{"configurations":{"run":{"adapter":"vscode-node","configuration":{"request":"attach","stopOnEntry":true,"protocol":"inspector","console":"integratedTerminal"},"breakpoints":{"exception":{"all":"N","caught":"","uncaught":"Y"}}}}}

上記設定後、coc-gauge の各種デバッグコマンドを実行すると vimspector が起動し、Debug が開始されます。

動画

実行
run

デバッグ
debug

Step参照箇所一覧
jump

Rename Step
rename

create-coc-extension

本機能は create-coc-extensionを使って作りました。

coc extension の開発方法は、公式にもどこにも情報がまとまっていなくて、create-coc-extension がなければ本機能を作れなかったと思います。感謝。
まぁ、各種APIの情報もないので、ロジックは型情報、コメント、既存のコードを見ながら手探りで実装する必要があるんですが。。。

参考記事

最後に

今回初めて coc の拡張機能を作り、初めて npm へのパッケージ公開をしました。vim, neovim などなど、長年OSSをありがたく使わせて頂いており、こんな素晴らしいものをタダで使っていていいのかなぁと常日頃思っていたのですが、本機能を公開することで少しでも恩返しになればと思います。

私みたいなおっさん世代は、Web上で情報公開することに抵抗感を持つ人も多いと思いますが、ていうか自分がそうでしたが、何事も恐れずにまずはやってみるといいのではないでしょうか。

以上、お粗末です。


vim内のヤンクをクリップボードと連携させるには

$
0
0

目的

vimとクリップボードを連携させる。
具体的にはvim内のコマンドのヤンク(y, dなど)によってコピーされる文字列をvimのレジスタではなくOS側のクリップボードに保存するよう設定する。

設定変更かあるいは環境構築か

そもそも今の環境で設定変更だけでリンクが可能かどうか調べる必要がある。不可能なら新しいプログラムなどのインストールが必要になる。terminalを開き、

$ vim --version | grep clipboard

で左上が+clipboardなら設定変更で可能、-clipboardなら不可能

不可能であった場合

もとから可能であったならこの項は飛ばして構わない。
bash
$ sudo brew install vim

で最新のHugeバージョンのvim 1をインストール
最初と同様にして確認、それでもまだ不可能ならソースコードからビルドすることになる。まずtarファイルをwgetでダウンロードする。ここではバージョンとしてはvim-8.1を扱っているが適宜置き換えるように。

$ wget ftp://ftp.vim.org/pub/vim/unix/vim-8.1.tar.bz2
$ tar xvf vim-8.1.tar.bz2

tarファイルの取得および解凍はどのディレクトリでも可
解凍したらvim81という名前のディレクトリが作られるのでそこに移動

./configure --enable-gui=yes--enable-multibyte--with-features=huge --disable-selinux--prefix=/usr/local --enable-rubyinterp--enable-xim--enable-fontset|grep gui

を実行する。少し長いが1行にまとめるように。最後に

$ make
$ sudo make install

をして完了
一旦ターミナルを閉じて再度確認し、+clipboardになっていたらOK 可能であった場合に進む。

可能であった場合

set clipboard&
set clipboard^=unnamedplus

をvimrcに追加することで実際に使える。vimrcは大抵ホームディレクトリに.vimrcとして存在するが、なければ新規作成すればよい。

あとがき

備忘録として書いたはいいものの、おそらく二度と使わない気がする。
ddを多用する人間なので非常に高頻度でクリップボードが汚染される。
不便で仕方なくて連携させたのに別の不便が増えて嫌な気持ちに...
なんで? なんでだろうね

参考

https://gist.github.com/nkurosawa/e74117241f43e7b4b6f1
https://pocke.hatenablog.com/entry/2014/10/26/145646

Hugeバージョン未満のものだと機能が制限されている。バージョンごとの大きさがそのまま形容詞の意味につながる


nginxのエラー「413 Request Entity Too Large」の解決方法

$
0
0

前提開発環境

  • AWSのEC2インスタンスを使用してデプロイ済み
  • Ruby on Rails6で開発

エラー概要

Rails6で投稿アプリを作成し、本番環境で投稿すると下記画像のエラーが発生しました。

nginx.png

画面を無駄に占有して413 Request Entity Too Largeとエラーが発生しました。
もうちょっと控えめな自己主張をして頂きたいものです((((;゚Д゚)))))))

投稿した画像サイズが大きすぎると怒られています。
画像サイズの設定を変更する為にEC2へログインします。

恒例のコマンドです。
以下を順にローカルのターミナルに入力していく。

①mkdir ~/.ssh

②cd .ssh/

③lsコマンドで、EC2で作成済みの<鍵名>.pemが表示される。

④chmod 600 <鍵名>.pem

⑤ssh -i <鍵名>.pem ec2-user@<EC2で発行したElastic IP>

すると、

  __|  __|_  )
       _|  (     /   Amazon Linux 2 AMI
      ___|\___|___|

と、上記の表示が出てEC2へのログインが完了。

viでnginxのファイル編集

今回はnginxの画像投稿許容サイズを引き上げる必要があります。
デフォルトが1MBまでなので、それ以上の容量を送信すると上記のエラーが起きます。

エラー解消の為に、Amazon Linuxに標準で装備されているテキストエディタviでファイル編集を行います。

EC2にログインしている状態で下記コマンドを実行する。
$ sudo vi /etc/nginx/nginx.conf

これでviが起動して、さらに編集したいファイルが表示されますが、結構長い表記のファイルです。その中でhttp {}の中で追記を行います。

ファイル表示しただけでは編集できないので、「i」を押してinsertモードに切り替えてファイル編集を行いましょう。

# /etc/nginx/nginx.conf
....中略

http {
    ....  
    .... #中略

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 4096;
    client_max_body_size 20M; #この項目を追記します。
    ....
    server {
        ....
        ....
    }
   ....以下省略
}

追記したら「esc」を押してinsertモードを終了し、「:wq」で変更を保存します。

ファイル編集は終わりましたが、あとは更新を反映させる必要があります。
今回のパターンは編集ファイルの反映が目的なので、nginxの再起動でなくリロードを行います。

sudo service nginx reload

これでviファイルへの編集が反映されたので、サイズが大きい画像サイズも送信が可能になります。viにはまだまだ馴染みが薄いので、慣れていきたいところです。

追記 viの操作方法

viエディタで実際の設定ファイルを編集する場合の流れ

1.「通常モード」でファイルを開く
2.「インサートモード」でファイルを編集する
3. 再び「通常モード」へ移行し、「:wq」で保存して終了する

通常モード
viエディタには「通常モード」と「インサートモード」があります。
通常モードは、viエディタにコマンドを打つことでファイルを保存したりviコマンドを終了したりできます。

「通常モード」のコマンド

コマンド    説明
:w  作成・編集したファイルを保存します。
:q  viコマンドを終了します。
:q! 編集した内容を保存しないでviコマンドを強制終了します。
:wq 編集した内容を保存してviコマンドを強制終了します。

i (インサートモード)
「通常モード」では、文字を入力することができません。文字を入力したい場合は、インサートモードにする必要が有ります。「I」キーを押すとインサートモードになり、文字の入力が可能です。

Esc
Escキーを押すと通常モードに戻ります。

Vimのメモまとめ

$
0
0

本記事の目的

自分が覚えたいVimのコマンドをまとめていきます。

開き方

コマンド内容メモ
view ファイル名ReadOnlyのモードで表示書き込みはできるが、保存しようとすると怒ってくれる。うっかり編集したときは:q!で閉じる。

閉じ方

コマンド内容メモ
:qvim終了編集した後にこのコマンドをするとE37: No write since last change (add ! to override)が表示される
:q!編集を破棄してvim終了
:w保存
:w ファイル名設定したファイル名で保存ファイルhogeで:w fugaをすると新しくファイルfugaが作成される
:wq保存してvimを終了

カーソルの動き

コマンド内容
k上向きに移動
j下向きに移動
h左向きに移動
l右向きに移動

文字列検索・操作

コマンド内容メモ
/文字列指定した文字列を検索.vimrcでset hlsearchを書いて置かないと検索結果が見づらい
:set hlsearch 検索結果をハイライトで表示:nohを打たないとハイライトがずっと表示される
:noh検索結果のハイライトを削除

WZ階層付きテキスト風折り畳み

$
0
0

概要

Vim 上で、WZEditor の階層付きテキスト形式(WzMemo)風の折り畳み、およびアウトライン表示を行うプラグインを作ってみました。

ついでと言ってはなんですが、以下の2つにもとりあえず対応させてあります。

  • Markdown の 行頭"#"の見出しレベル
  • マーカーによる折り畳み(規定では「{{{」と「}}}」で挟まれたブロック)

ワタクシ、プログラミングだけではなく、日本語の文章も Vim で書いているので、こういうのがあるといいなーと思って書きました。

ついでに Vim プラグインを初めて書いた人間の視点というものを詳らかに記していこうと思います。
そのため、ガリガリとプラグインを書いているという人から見たら、稚拙な手法を採用している可能性も多々ありますが、これからプラグイン書いてみたいぜ!という人の一助になれば幸いです。

「ああ、こんなノリでもプラグインらしき物が作れちゃうんだなー」という感じで。

という事なので、Vim script をよくわかっている人が書いた記事ではなく、ド素人が紆余曲折した経緯だと言うことに留意しておいてください。

実はもっとスマートな方法があるにも関わらず、回りくどく書いてしまっていて読みにくい箇所もあるでしょうし、妙にテクニカルに凝っていない分、朴訥な書き方で、改造などもしやすいかもしれません。とセルフフォロー。

環境

GVIM 8.2.1287 (kaoriya 2020/07/24)

自前の_vimrc_gvimrcの内容と干渉して上手く動いているだけかもしれないので、変なところがあれば報告をいただければ確認するかもしれません。

そもそもアウトラインって?

超適当な説明なので、少しでも意味が分かっている人は読み飛ばしてもよい項です。

アウトライン、とは、「章・節・項」などの文章の階層的な構造を一目で見られるようにするものです。
アウトラインがあると、文章全体の流れや構成を整理しやすくなります。

書いてる人間にとって整理しやすくなったからといって、他者にとって読みやすい文章になるかどうかは別問題であるというのは、悲しいことに本稿がテキメンに表しているとおりでありますが。(閑話休題)

その「アウトライン」を表示する機能と、文章編集機能を兼ね備えたソフトウェアの事を、「アウトラインプロセッサ」ナドと呼んだりするようです。

「文章を全体の枠組み(アウトライン構造)から考えるためのソフトウェアである」とかどうとか言われていますが、物としては各々のフォルダ内に一個だけの文書ファイルしか扱えないエクスプローラのようなものです(暴論)

操作する要素としては、「見出し(ノード)」と、「その見出しに対してブロック化された文章」の2つに大別されます。要するに「フォルダ」と「ファイル」みたいなもんです。

まずは効果を感じてみたいという場合は、「起・承・転・結」とか「序・破・急」だとかの見出しを作るだけですら、その即効性・有用性を感じる事が出来ると思います(かな?)

もう少し具体例でいえば、・・・例えば、読書感想文を一気に800文字を書くよりも、

【読書感想文】
├1.この本を選んだ理由 (100~300文字程度)
│ ├この作品のジャンルや、自分の好み分野について
│ ├作品を知った経緯
│ ├作家について興味を持った点等
│ └:
├2.あらすじ説明 (200~300文字程度)
│ ├導入(起承転結の「起」程度)
│ └見所とその感想
├3.感情移入した登場人物 (200~300文字程度)
│├好感を持てた人物
││ ├人物説明やエピソード引用
││ └好きな理由
│└好感を持てなかった人物
│  ├人物説明やエピソード引用
│  └好きになれない理由
└4.最後に (100~300文字程度)
  ├作家に対して共感 or 反感を持った点
  ├自分が登場人物ならどうしたと思ったか、とか
  └読み終えて自分はこれからどうしていきたいと思ったか、とか

みたいな感じで。

このような雑なアウトライン程度なら、何パターンでも考えだすことができると思います。
書いていくうちに、ノードを足したり引いたり入れ替えたりすることで、アウトライン自体も変わっていくでしょう。

いろいろなアウトラインで書いてみて、最終的に一番しっくりきた感想文を提出することすらできるようになるはずです。

一本の読書感想文を書くのにもヒーヒー言っていたはずなのに、もはや複数の感想文を容易に捏ね上げて、うまくいっている感じがするものを提出する、というスタイルにまで持っていける可能性があるわけです。
小学生のころに知りたかった!

ノードツリー と テキスト

アウトラインプロセッサの構成要素について説明します。

  • 階層化されたノードのツリービュー(またはリスト)があります。エクスプローラのフォルダツリーのような感じです。
  • ノードのテキスト内容を表示する領域があります。エクスプローラのファイルのリストビューの代わりに文章が一個デーンとある感じです。

以上!

ノードツリーでは、ノードを同一階層内で上下させるどころか、別階層へもホイホイと移動させることができたりします。

例えば、ノードを分けておいた単位で、「あ、これは先に書いとかんといかんヤツや」とか「これは別の章で説明した方がいいかも?」とか、思い立ったが吉日、ペイッと簡単に動かすことができるわけです。

もちろん辻褄が合うように細かいところの修正は必要ですが、「長ーい1つのファイル」の中身を切った張ったするよりかは、大筋を見失いにくい視点を保ったまま作業を進めることができます。

ノードツリー

ノードツリーが、フォルダツリーと違うのは、上から下へ向かう順序関係があるくらいです(という程度の認識の人間がこの記事を書いています)。

このノードツリーの部分が「アウトライン」と呼ばれ、文章全体の構造・構成を表します。

GUI を備えたアウトラインのノードツリーでよくありそうな機能は、ドラッグ&ドロップすることでテキスト内容をコネコネとできるようなやつです。任意のノードを並び順を変えるどころではなく、別のノードのサブノードへ、ヒョイと持って行ったりできてしまいます。

ちなみに、拙作プラグインには、そんな機能はありません。ノードを移動させたい場合は、テキスト上にて、zcで折り畳んでから ddして、貼り付けたい場所でpPしてください。編集機能は素の vim 頼みです。もしや、何か不満があるでしょうか?いいえ vimmer なら無い筈ですね。

(アウトライン表示上でddとかやると、テキストの該当位置に移動してからzcddする…という案もあったのですが、アウトライン表示とテキストの折り畳み状態が完全には同一にはならないことがあるので、考えるのを止めました。具体的に言うと、WzMemo 形式の同一階層の見出しが空行無しで連続している場合とか、Markdown のコードブロックの中にコメントなどで行頭「#」が入っちゃう場合などです。折り畳み機能を使わず、自前でアウトラインの情報から自ノード配下のテキストを対象に切り取る事もできるのかもしれませんが、今のところ、そこまでやる気はないです。)

また、別階層へ移動させた場合には、自分でノードのレベルを変えてやる必要があります。(マーカーの入れ子で折り畳んでいるときにはあんまり関係ないんですけれどもね)

一応、再帰的にノードの階層を変更するコマンドも用意してありますが、一度目の階層変更により同一階層になったノードが、次の階層変更コマンドの範囲に加わってしまうため、安易に何度も使うとアウトラインがぐちゃぐちゃになる恐れがあります。

テキスト

テキストは、ノードに対応する文章のブロックです。

該当ノード分のみのテキストしか表示されないものもあれば、全文が連続してダーっと入っていて、選択したノードの箇所が表示されるものもあります。拙作プラグインは後者のように振舞います。

世のアウトラインプロセッサ達は、当然、テキストエディタ的な編集機能を持っています。装飾も行なえるようなワープロ的な編集機能、というか、もはやそれどころではない機能を持っているものもあります。画像が貼れたり、リンクが張れたり。果ては音声や動画まで張れたりするかも!(いわゆるハイパーテキストですな)

・・・拙作プラグインでは、当然そんなことが出来るようになるワケがなく、純然たるプレーンテキストファイルだけが対象です。そもそも、WZMemo 形式ってそういうものですしね。

いわんや、本稿の出発点からして、Vim のパワフルな編集機能の恩恵を一身に受けながら編集作業を行う、その事こそが至上命題なのです。

「アウトラインプロセッサのテキスト編集機能が、多少 Vim ライクになっている」という程度の事で満足ができるのなら、こんな事はしていません。

Vim を用いたアウトライン編集への歩み

冒頭の繰り返しになりますが、私、Vimmer の末席に辛うじてその身を置かせてもらっている弱卒でありますからして、プログラミングは勿論のこと、日本語の文章だろうが、議事録だろうが、日記だろうが、GTD だろうが、バレットジャーナルだろうが、兎に角なんでもかんでも Vim で書きたいという、ごくごく自然な欲求を持って生きています。幸運にも生涯の伴侶とも言うべきエディタと出会うことができた者の末路健やかで平和な日々の暮し方と言えるでしょう。

プログラミングを行う時には、ctags を使ったりする程度で、まぁまぁ満足快適に過ごしていたのですが、問題は日本語の文章を書く時です。

それなりに長い間、折り畳み機能を使って、それなりに過ごしてまいりましたが、折り畳み機能を使っていけばいく程に、アウトライン表示機能がどんどんどんどんと欲しくなってきてしまいました。

折り畳み表示(folding)とは

Vim の持っている機能です。読んで字のごとく、行内容を折り畳むことで、一時的に非表示にします。

そうすることで、長い文章でも見通し良く編集を行うことができます。

Vim のイカした点として、折り畳んだ状態で、ddpとかすると、一塊の単位でカット&ペーストできるので、推敲にも重宝します。そのまま >> とかでインデントとかもできちゃいます。折り畳んだ行に対して、s/xx/yy/gとかやると、そのヒトカタマリの内部だけを対象に置換できちゃいます。非常にイカス機能です。

Vim をアウトラインプロセッサ的に使おうとした場合、目の付け所にならないワケがない機能と言えるでしょう。言えるよね?

折り畳みの方式(foldmethod)として、手動(manual)、インデント(indent)、式(expr)、マーカー(marker)、構文(syntax)、差分(diff)があります。

アウトラインプロセッサとして使おうとした場合の折り畳み方式は、インデント、または、マーカーが選択肢になるでしょう。

ということで、インデントとマーカーについてだけ簡単に説明をば。
(他の折り畳み方式については割愛します)

インデントによる折り畳み

最初はインデントによる折り畳みを使って、何とか Vim をアウトラインプロセッサとして使ってみようと頑張ってみました。
何と言っても字下げを行うだけで、アウトラインを表すことができるのなら、というのが魅力的でした。

Vim をアウトラインプロセッサ的に使いたい、というような目的でググると、「fdm=indentでおk。」みたいなものもチラホラとヒットします。(年代的なものもありますし、もちろん VOoM や unite-outline みたいな、プラグインを導入している人もいますけどね)

その頃の私はまだプラグインという存在そのものを食わず嫌いしており、ネイキッドな Vim で何とかなるんじゃないのかと甘く考えていたのですが、インデントの場合には、以下の問題があり断念しました。

例えば、このようなファイルの場合・・・・

title
    index1
    text1
        index1-1
        text1-1

        index1-2
        text1-2

    index2
    text2

各行の「折り畳みレベル(foldlevel)」は、こうなります。

  1 title                   foldlevel=0
  2     index1              foldlevel=1
  3     text1               foldlevel=1
  4         index1-1        foldlevel=2
  5         text1-1         foldlevel=2
  6                         foldlevel=2 ←同一階層で見出しを分けたいが、分けられない。ここで畳むと「index1-1」で畳まれる
  7         index1-2        foldlevel=2
  8         text1-2         foldlevel=2
  9                         foldlevel=2
 10     index2              foldlevel=1 ←同上。ここで畳むと、「index1」で畳まれる
 11     text2               foldlevel=1

同一の折り畳みレベルが続く間は、一つの折り畳みになってしまうため、同一階層内に複数のノードを持つことができないという事になります。

また編集状態によって、折り畳まれる範囲が繋がるのか繋がらないのかに差が出ることがあります。

上記の例の場合、一気に1~11行目までを張り付ければ上のようになるのですが、いったん6~7行を切り取ってから、張り付けなおすと、7行目はいきなり foldlevel=2 から始まり、2行目から始まる折り畳みと泣き別れになる、というような事態に遭遇します。

インデントによる折り畳み用の foldexpr を自分なりに書いてみても良いかもしれませんね。

ちなみに、当プラグインのアウトライン表示側の Folding は manual でシコシコと自前で折り畳んでおります。
空行の混入を想定する必要がなく、 nomodifiable に指定してあるアウトライン表示ならではの泥臭い対処法ですね。

随時変更が加わるテキストに対して、全行捜査が必要な処理を foldexpr として登録するわけにいきませんからねぇ。

マーカーによる折り畳み

次は、マーカーによる折り畳みで、何とか Vim 上でアウトラインプロセッサのようなことを実現しようと思いました。

インデントに比べると、アウトライン構造を確実に表すためにテキストに付与される情報が増えてしまいますが、それでも ネイキッドな Vim で実現できるエコでクリーンで強力な仕組みという点に抗いがたい魅力を感じていました。

Vim をアウトラインプロセッサ的に使いたい、というような目的でググると、「:set fdm=markerして、後は :help foldingを読めばおk。」みたいなものもチラホラとヒットします。

で、まぁ、確かに、ぶっちゃけこれで問題はありませんでした。

デフォルトの閉じた折り畳み行の可読性的にやや難ありなことと、デフォルトのマーカーである{{{}}}が若干ウザいことを除けば、ほぼほぼ満足できるものです。

閉じた折り畳みのデフォルト表示(foldtext()関数)が気に入らなかったことについては、foldCC.vim が殆ど解決してくれました。

マーカーも、ハイライトを工夫するなどの工夫は必要でしたが、見慣れてこればそれほど気にならなくなってきます。

ソースファイル等では、折り畳みマーカー部分がコメントアウトされていても問題なく折り畳みできるところや、階層の入れ子が明確でノードの移動にも強い、という優れた点もあります。

もはや残された課題は、非 Vimmer (観測範囲内では私以外の全員)から、ナニコレ扱いを受ける程度の些事だけでした。(モードライン程度の追加情報なら気にならない人達でも、テキストの至る所に{{{}}}が記入されていると流石にギョッとするようです)

しばらくハッキリと「不満がある」とまでは言えないが、完全に満足したわけでもない状態で過ごしていましたが、やはり、もっと簡単に折り畳みができないかと思い、ふとした弾みに、WzMemo 風の折り畳みを自作できないだろうか、と考えるようになりました。

このマーカー折り畳みにより実現された「微妙に快適なテキスト編集環境」が、より快適な折り畳み、および、アウトラインへの渇望をもたらしたといっても過言ではないでしょう。人の欲とは限りないものですね。

ちなみに、この foldCC.vim のおかげでプラグインという方向性への忌避感がだいぶ薄らぎました。・・・後は rogue.vim とか?
とはいえ、未だにプラグインマネージャを入れてプラグインをガンガン入れる、という気にまではなりませんが。

なにせ、未だに自由にインターネットに繋がらない環境に飼殺されている社畜でありますからして。(閑話休題)

WZ階層付きテキストとは

行頭から続く「.」の数で、ノードの階層を表す形式です。
一見しただけでは、ただのプレーンテキストファイルです。実にシンプルイズベスト。
アウトラインがメタ情報ではなくテキストとしてガッツリ書かれており、それでいて視覚的にそれほど主張せず、かつ階層を直観的に指定可能です。

プログラムソースの場合はマーカー折り畳みでも問題ないのですが、文書を書いている場合にはマーカーがどうにも邪魔に感じてしまったり、zfzdの操作や、閉じマーカーのインデント量を調整する手間が面倒に感じてしまうことがあり、この WzMemo 形式を Vim 上で実現したいと考えるようになりました。

かつて WZEditor ユーザーだったこともあり、この形式のテキスト資産を保有していることも理由に挙げられます。

ググったところ、foldexpr にチョロっとワンライナーして、1階層のみ対応させているものは見つかったのですが、2段3段・・・とネストできるものは見つかりませんでした。

そこで、まず最初に WzLikeOutline() という foldexpr 関数を自作しました。
その勢いで勉強がてらにプラグインの形にしてみようと思い立ち、アウトライン表示部分まで作りました。

WzMemo 形式を使用する事で得られる副次的な効能として、Windows上で幅を利かせているエディタ等でもサポートされている場合があるため、比較的説明が楽、かつ、肩身の狭い思いをしなくて済みます。

本家の WZ Editor では、行頭の「.」だけだったかどうか記憶が曖昧ですが、拙作プラグインでは行頭から空白文字でインデントされていても見出しとして扱うようにしてあります。

※Markdown 編集時はインデントされている「#」は見出しとして扱わないようにしてあります。

本家 WZ Editor では下記のような事ができたはずですが、残念ながら拙作プラグインでは正しく折り畳みできません。
アウトライン表示はできるのですが、折り畳みはできないのです。
同一の折り畳みレベルの行が連続していると、ひと固まりで折り畳まれてしまうのです。

例えばこんな文章の場合

.見出し1
..見出し1-2
ほげほげ
..見出し1-3
ふがふが

こういう状態になっています。

.見出し1         foldlevel=1
..見出し1-2    foldlevel=2
ほげほげ          foldlevel=1 ←次行が行頭"."始まりなので、現在の折り畳みを終了させるため、折り畳みレベル-1
..見出し1-3    foldlevel=2
ふがふが          foldlevel=2

「ほげほげ」の行を折り畳むことができません。
正しく折り畳んでもらうには、こうする必要があります。

.見出し1         foldlevel=1
..見出し1-2    foldlevel=2
ほげほげ          foldlevel=2
                  foldlevel=1 ←上記の事象を回避するために、空行を挟む必要があります。
..見出し1-3    foldlevel=2
ふがふが          foldlevel=2

この情けない実態を見ればお察しな通り、実際にはプラグインという程の物ではなく、foldmethod=exprとして、foldexpr=func_HOGE()として、50行足らずのfunction! func_HOGE()~endfunctionを vimrc に書き加えるだけで実現可能なものです。(厳密にはもうちょっとアレコレありますが、大体そんなもんです。)

残りのプラグインの行数は、ほぼアウトライン表示に関わる処理に費やされています。

設定方法

ファイルの配置と、vimrcへ2行追記するだけの簡単な作業です。

~/_vimrc

以下の2行を追記してください。

set foldtext=MyFoldText()
set fillchars-=fold:-

foldtext は 折り畳まれた行の代わりに表示される文字列を返す関数です。
高名なものとして foldCC が存在します。

基本的には、「見出し行の内容をそのまま表示+折り畳み情報」というのが理想なのですが、foldCC では tab インデントされているものが正しく表示されない(?)ようなので、そのあたりを自分なりに書き直したものが、 myFoldText() という捻りのない名前の関数です。

fillchars オプションの fold キーワードは、'foldtext' での空白部分を示します。
必須の設定ではありませんが、規定値が '-' なので無駄な装飾が行われないように取り除いておきます。

WzLikeOutline.vim

~/vimfiles/plugin/配下に、以下のWzLikeOutline.vimを格納してください。

私はもっぱら Windows 利用者なので~/.vimではなく~/vimfilesになりますが、その辺りはruntimepathを踏まえて適当に読み替えてください。

なにせ職場のサーバに入っているのが Vim version 6.x で、folding すら使えないので、端末で gVim を使うのです。HP-UX で、素の vi しかなかった頃よりは全然マシなので、なんにも問題はございません。ええ、ございませんとも。

まずはスクリプト本体。
本体しかないとも言う。(お行儀が悪いかもしれませんが、autoloadに分けるほどでもないかと?)

各部の解説は後述します。

WzLikeOutline.vim
" vi: set fdm=marker noet tw=0 : "Vim global plugin for outline like WzMemo"Last Change:2020/10/09 22:00:17."Maintainer:azuwai"License:This file is placed in the public domain."二重ロード避けif exists("g:loaded_outline_like_wzmemo")finishendifletg:loaded_outline_like_wzmemo=1lets:save_cpo=&cpo
set cpo&vimlets:winid_ol=1  "とりあえず1に?
lets:flg_switch=0lets:flg_leave=0"デフォルトでWZ階層付きテキストを有効にすると、Netrwでディレクトリが折り畳まれ"てしまうので、txt、mdファイルを開いた時だけにしてあります。"(そもそもファイル名がないとアウトライン表示に使っているロケーションリストが正しく動かない)"【初期設定】以下を vimrc へ追加。"set foldtext=MyFoldText()"set fillchars-=fold:-              "折り畳み行に無駄な装飾が行われないようにlets:treeTitle='Outline'lets:treeIndent=2auBufNewFile,BufRead *.txt setl fdm=expr fde=WzLikeOutline(v:lnum,'.',1)auBufNewFile,BufRead *.md  setl fdm=expr fde=WzLikeOutline(v:lnum,'#',0)auBufNewFile,BufRead *.txt com! MOT call<SID>makeOutlineTree('.',1)auBufNewFile,BufRead *.md  com! MOT call<SID>makeOutlineTree('#',0)auBufNewFile,BufRead *.vimcom! MOT call<SID>makeOutlineTree('',0)        "fdm=marker想定
auBufNewFile,BufRead *.sql com! MOT call<SID>makeOutlineTree('',0)        "fdm=marker想定


"折り畳み見出し行function! MyFoldText() abort "{{{let line=getline(v:foldstart)                                       "現在行の内容を、そのまま見出し文字列に
    let line = substitute(line,'\t',printf('%'.&ts.'s',' '),'g')    "タブ文字をtabstop数分の空白に置換
    let tail ='[Lv:'.v:foldlevel .']'                                 "追加情報(階層)
    let tail = tail .'('. eval('v:foldend - v:foldstart+1').'行)'        "追加情報(折り畳み行数)

    let myWidth= winwidth(0)                                            "画面幅
    let myWidth -=&fdc                                                 "折り畳み表示幅を考慮
    if&nulet myWidth -=&nuw                                             "行番号表示時は、行番号の桁数を考慮
    enlet margin  = myWidth  - strlen(line)- strlen(tail)                "画面幅、文字列長、追加情報から、余白長を求める

    if(&tw !=0)&& (&tw <(margin + strlen(line)+ strlen(tail)))let margin -=(margin + strlen(line)+ strlen(tail))-&tw
    endiflet tail = printf('%'. margin .'s',' '). tail                     "追加情報を、画面の右端に追加
    return line.tail                                                    "(highlightのguibgをnormalと少し変えておくとわかりやすい)endfunction "}}}" WZ階層付きテキスト(WzMemo)風折り畳みfunction! WzLikeOutline(lineno,lv_mark,indentable) abort "{{{let line = getline(a:lineno)        "現在行を取得
    ifa:indentable==1                "行頭空白除去指定時
        let line= substitute(line,'^[[:blank:]]\+','','')enletlv=0leti=0whilei<=&fdc                     "行頭からfdcまで先頭から'.'の数を数える。
        if line[i]==a:lv_markletlv=lv+1elsebreakendifleti=i+1endwhileiflv==0                            "現在行が、見出し行ではない場合、
        let line = getline(a:lineno+1)  "次の行を取得
        ifa:indentable==1            "行頭空白除去指定時
            let line= substitute(line,'^[[:blank:]]\+','','')enletlv=0leti=0whilei<=&fdc                 "行頭からfdcまで先頭から'.'の数を数える。
            if line[i]==a:lv_markletlv=lv+1elsebreakendifleti=i+1endwhileiflv==0                "次の行も見出し行ではない場合。
            return"="          "上の階層を継続
        else                    "次の行が見出し行の場合、階層を区切るために、
            returnlv-1         "次の見出し行よりも1つ低い階層にする。
        endifelse                        "見出し行だった場合、自行の階層を返す。
        returnlvendifendfunction "}}}"アウトライン表示function!s:makeOutlineTree(lv_mark,indentable) abort "{{{ifs:isOutllineList()==1
        exec "normal \<CR>"endif"折り畳み方法の判定if(&fdm !='expr')&& (&fdm !='marker')
        echo 'ERROR : only - set ''foldmethod'' = "expr" or "marker"'returnendif"折り畳み方法に応じた検索パターンを編集。ついでに見出し行のハイライト設定もif(&fdm =='expr')let gptn='\M'.a:lv_markif(a:indentable==0)let gptn2 = gptn
            let gptn ='^'. gptn
        elselet gptn2 ='\m[[:blank:]]*'. gptn
            let gptn ='^\m[[:blank:]]*'. gptn
        enif&ft !="markdown"            "Markdownは標準のハイライトがあるのでそっちを優先
            exec 'syn match MyFoldSt "'. gptn .'\m.*"'hi link MyFoldSt FoldLabel
        endifelseif(&fdm =='marker')let fmrs = split(&fmr,',')let gptn ='\M'. fmrs[0]let gptn2 = gptn
        exec 'syn match MyFoldSt ".*'. gptn .'\m.*"'"FoldLabel は .gvimrc で好きに定義するようにhi link MyFoldSt FoldLabel
    endiflet cfdm=&fdm

    "ロケーションリストを初期化
    exec "lexpr('')""ロケーションリストに追加"exec 'g/' . gptn . '/lad expand("%") . ":" . line(".") . ":-" . getline(".") 'let codenotation=0foriin range(1,line("$"))let wkline= getline(i)if stridx(wkline,'```')==0let codenotation=(!codenotation)endifif!codenotation && match(wkline,gptn)!=-1
            exec 'lad expand("%") . ":" .'.i.' . ":-" . getline('.i.')'endifendfor"ロケーションリストを閉じて開き直す
    exec 'lclose'vertlwindowlets:prev_winnr=winnr('#')lets:winid_ol= win_getid()call setloclist(s:winid_ol,[],'a',{'title':s:treeTitle})"ロケーションリストをおめかしsetl fdc=0 nonu
    setlma
    exec '%s/^'. expand("#").'|//e'let lastline= getline('$')              "最終見出しの、
    let nucol = stridx(lastline,'|')+1      " |区切りが出る位置までをtabstopに設定
    exec 'se ts='. nucol
    %s/| -/|/e                             " |区切りの位置を揃える
    se et                                   "空白文字に変換
    %retab!
    %s/ |/|/e                               "余分な空白を削除
    %s/^\([0-9]\+\)\([^0-9]\+\)|/\2\1|/e    "桁位置揃え
    exec 'setl ts='.s:treeIndent"折り畳みマーカーを削除if(cfdm =='marker')
        exec '%s/\M'. fmrs[0].'//'elseif(cfdm =='expr')"exec '%s/|\M' . a:lv_mark . '/|/e'
        exec '%s/|'. gptn2 .'/|/e'
        exec '%s/\M'.a:lv_mark.'/   /eg'endif
    %retab!redraws     "TODO:「続けるにはENTERを押すか~」で止まりたくないが、描画は抑止したままにしたい。
    "message clearcalls:makeFoldOutline()setl noma
    setl nomod

    letl:MaxWidth =0foriin range(1,line("$"))let myWidth =  eval(strlen(getline(i)))ifl:MaxWidth < myWidth
            letl:MaxWidth = myWidth
        endifendfor
    exec 'setl winwidth='.l:MaxWidth
    exec 'setl winminwidth='.(&wiw <30 ? &wiw :"30")"ロケーションリスト移動のリマップを追加
    noremap <silent>[f:<C-u>lab<CR> \|zv \|z<CR>
    noremap <silent>]f:<C-u>lbel<CR> \|zv \|z<CR>
    noremap <silent>[F :<C-u>lfir<CR> \|zv \|z<CR>
    noremap <silent>]F :<C-u>llast<CR> \|zv \|z<CR>
    noremap <silent><Tab>:call<SID>switch_outline()<CR>if cfdm =='expr'
        exec 'noremap <silent> z< :call <SID>incFoldLevel(-1,'''.a:lv_mark.''','''. gptn .''','.a:indentable.')<CR>'
        exec 'noremap <silent> z> :call <SID>incFoldLevel(1,'''.a:lv_mark.''','''. gptn .''','.a:indentable.')<CR>'endif

    exec s:prev_winnr."wincmd w"endfunction "}}}function!s:makeFoldOutline() "{{{setl fdm=manual
    normal zE
    let lv_max=0foriin range(1,line('$'))let lv_mod =0let lv_now =s:getFoldLevelOutline(i)forjin range(i+1,line('$'))let lv_pos =s:getFoldLevelOutline(j)if lv_now==0&& lv_pos==0breakelseif lv_pos < lv_now
                breakendifif lv_now != lv_pos
                let lv_mod =1endifendfor"echom i . '(' .lv_now . ') - ' j . '(' . lv_pos . ')'if1<(j-i)&& lv_mod==1"echom 'folding! ' . i . ' - ' . (j -1)
            normal zR
            "exec i . ',' . eval(j-1) . 'fold'
            exec i
            normal V
            exec "normal ". eval(j-i-1)."j"
            normal zf
            if lv_max < foldlevel('.')let lv_max = foldlevel('.')endifendifendforlet lv_max +=1
    exec 'setl fdc='.(lv_max <=12 ? lv_max :12)
    normal zR
endfunction "}}}function!s:getFoldLevelOutline(lineno) abort "{{{"message clearlet line=getline(a:lineno)letstart=stridx(line,'|')+1        "見出し文字列の先頭から
    "echom 'start:' . startleti=startwhilei< strchars(line)            "文字数分 ≠Byte数
        "echom i . ':' . strcharpart(line,i,1)if strcharpart(line,i,1)!=' ' "非空白を発見するまで
            breakendifleti+=s:treeIndentendwhile"echom 'return:' . (i-start) / s:treeIndentreturn(i-start) / s:treeIndentendfunction "}}}"折り畳みレベル一括変更 ※今のところ、+1,-1のみfunction!s:incFoldLevel(delta,lv_mark,gptn,indentable) abort "{{{ifmatch(getline('.'),a:gptn)==-1     "現在行が見出し行ではない場合、
        labove                              "直前の見出し行へ移動
    endiflet save_line = line('.')               "見出し行の位置を保持

    "折り畳みをすべて開く(foldlevelを正しく取得するため)
    normal zR

    let save_foldlv = foldlevel(line('.'))  "現在行の折り畳みレベルを取得
    "echom getline('.') . '(' . save_line . ') Lv:' . save_foldlvifa:delta<0                          "階層を下げる場合は、予め引いておく。
        let save_foldlv-=1endifsetl lazyredraw
    foriin range(line('.'),line('$'))     "現在行から最終行へ向けて、
        "echo i . "行目(" . foldlevel(i) . '/' save_foldlvif  foldlevel(i)< save_foldlv      "現在のレベルよりも下がるまで続ける
            breakendififmatch(getline(i),a:gptn)!=-1   "現在行が見出し行の場合
            ifa:delta<0
                exec i.'s/\M'.a:lv_mark.'//e'else
                exec i.'s/\M'.a:lv_mark.'/'.a:lv_mark.a:lv_mark.'/e'endifendifendfor
    exec 'call s:makeOutlineTree('''.a:lv_mark.''','.a:indentable')'setl nolazyredraw

    exec save_line
    normal zM
    normal zO
    normal zt

endfunction "}}}"アウトラインウィンドウ切替function!s:switch_outline() abort "{{{let winid_text =  getloclist(s:winid_ol,{'filewinid':0}).filewinid

    "アウトライン表示の場合ifs:isOutllineList()==1let winnr_text = win_id2tabwin(winid_text)[1]
        exec winnr_text ."wincmd w""テキスト表示の場合elseif winid_text == win_getid()let winnr_ol = win_id2tabwin(s:winid_ol)[1]
        exec winnr_ol ."wincmd w"endifendfunction "}}}"ウィンドウに入った直後auWinEnter * calls:myWinEnter()function!s:myWinEnter() "{{{"アウトライン表示の場合。ifs:isOutllineList()==1"Ctrl-wやマウスクリックでアウトラインが選択された瞬間に、同期されるのを抑止lets:flg_switch=1ifs:flg_leave==1
            exec "quit"endif"アウトライン表示ではないelse"テキスト表示を閉じた後、他のウィンドウがアクティブになった場合ifs:flg_leave==1let winnr_ol = win_id2tabwin(s:winid_ol)[1]
            exec winnr_ol ."wincmd q"endifendifendfunction "}}}"アウトライン同期auCursorMoved * calls:outlineSync()function!s:outlineSync() abort "{{{ifs:flg_switch==1lets:flg_switch=0returnendifset lazyredraw

    "echom "outlineSync! filename:" . expand("%") . '(' .  line('.') . ')'"ロケーションリストの場合ifs:isOutllineList()==1letl:location = getloclist(s:winid_ol)[line('.')-1]"echom 'outlineSync!! outline  ' . expand("%") . getline('.') . '('. l:location.lnum . ')'"アウトラインに該当するテキストを先頭に表示してカーソルを移動させる。
        exec "normal \<CR>"
        normal zt

        "直前のウィンドウ(アウトライン)へ戻る。letl:prev_winnr=winnr('#')
        exec l:prev_winnr ."wincmd w""echom "outlineSync!!-!" . expand("%") . getline('.')"ロケーションリストではなく、かつ、アウトラインのロケーションリストが存在elseif win_id2tabwin(s:winid_ol)[1]!=0&& getloclist(s:winid_ol,{'title':0}).title ==s:treeTitle"echom "outlineSync!! text  outline_winid" . getloclist(s:winid_ol,{'filewinid' : 0}).filewinid . ' , text_winid:' .  win_getid()"ロケーションリストが表示しているファイルのウィンドウの場合のみif  getloclist(s:winid_ol,{'filewinid':0}).filewinid == win_getid()trylet save_wi = winsaveview()"見出し行上では、一つ上のアウトラインを指してしまうため、1行下へ移動して、上のリストへ移動。
                exec "normal \<CR>"
                exec ":labove"catch/^Vim\%((\a\+)\)\=:E553:/ "「要素がもうありません」 先頭で要素がもう無い場合
            catch/^Vim\%((\a\+)\)\=:E42:/  "「エラーはありません」  gf 等でウィンドウ内が別バッファになった時。
                set nolazyredraw
                returnfinallycall winrestview(save_wi)endtryendifendifset nolazyredraw

endfunction "}}}auBufWinLeave * calls:myBufWinLeave()function!s:myBufWinLeave() "{{{trylet winid_text =  getloclist(s:winid_ol,{'filewinid':0}).filewinid
    catch/^Vim\%((\a\+)\)\=:E716:/ "辞書型にキーが存在しません アウトラインが表示されていない場合。
        returnendtry"閉じられようとしているバッファと今いるバッファが一致していない場合if expand("%")!= expand('<afile>')     " :lclでテキスト側からアウトラインを閉じようとした場合等
        returnendif"アウトライン表示以外、かつ、アウトラインが示しているウィンドウの場合ifs:isOutllineList()==0&& winid_text == win_getid()"exec "lclose"  E855になるので無理。フラグを立てておいて、WinEnterで判定させる。lets:flg_leave=1endifendfunction "}}}"アウトライン表示のロケーションリストかどうか判定function!s:isOutllineList() "{{{if&buftype !='quickfix'                   "quixfix or location-list
        return0endiflet wi = getwininfo(win_getid())[0]if wi.loclist !=1                          "location list!return0endiftrylets:loctitle= getloclist(s:winid_ol,{'title':0}).title
    catch/^Vim\%((\a\+)\)\=:E716:/ "辞書型にキーが存在しません アウトラインが表示されていない場合。
        return0endtryifs:loctitle!=s:treeTitlereturn0endifreturn1endfunction "}}}let&cpo =s:save_cpo

解説

使い方

まずは使い方から説明します。
各関数の中身については、後のセクションで説明します。

起動、アウトライン再描画

:MOTとしてください。左側にアウトラインが表示されます。

txt拡張子を WzMemo 形式として編集している最中に、.を押下してノードを作成したり、階層を変更した場合などは、明示的に:MOTを実行してアウトラインを再描画する必要があります。

現在は、以下の拡張子を編集している場合に、本機能が有効になっています。

拡張子階層指定文字階層指定文字のインデント可否説明
txt.WzMemo 形式テキスト。アイディアメモでも備忘録でも、議事でも日記でも作文でも、なんにでも使えます。
md#Markdown。見出しレベルを階層としてアウトライン表示します。```で囲まれたコードブロック中の行頭「#」には反応しないようにしてあります。(折り畳みとしては扱われてしまいますが、アウトラインのノードとは見なしません)
vimfoldmarkerに従うVim Script 編集時に、fdm=markerとして関数単位で折り畳んでおくと便利です。
sqlfoldmarkerに従うSQL ファイルを編集する事があったので、追加してみました。

ショートカット

アウトライン表示が存在する状態で、以下のショートカットが有効になります。

テキスト上

キー機能
tabアウトライン表示のウィンドウに移動します。
[fテキスト上で押下すると、上方向の直近のノードへ移動します。
]fテキスト上で押下すると、下方向の直近のノードへ移動します。
[Fテキスト上で押下すると、先頭のノードへ移動します。
]Fテキスト上で押下すると、最後尾のノードへ移動します。
g>現在のノードの階層を再帰的に+1し、アウトラインを再描画します。
g<現在のノードの階層を再帰的に-1し、アウトラインを再描画します。

テキスト上でカーソルを移動すると、アウトライン表示側も追尾して反転表示になります。

アウトラインの元になっているテキストを表示しているウィンドウが全て閉じられた場合、アウトラインも自動的に閉じます。

アウトライン上

キー機能
tabテキストに移動します。カーソル位置は移動しません。
Enterテキストに移動します。該当ノードの先頭にカーソルを移動します。

アウトライン上で、上下移動(jk等)すると、テキスト側は該当ノードをウィンドウの最上行にして再描画されます。

該当ノードが折り畳まれていた場合は、折り畳まれていた行を最上行として、カーソル位置をノードの行に合わせようとします。

関数説明

ざっくりとした説明になります。
本体を修正した時に、この部分の修正まで気が回らず、以下に書いてある内容が古いままの可能性があります。。

関数外

この関数の外の部分だけはコードを引用して少し解説してみたいと思います。
※関数の中に入ったら、コメントでも読んでもらった方が確実だと思うので、要点のみ述べます。

二重ロード除け

"二重ロード避け
if exists("g:loaded_outline_like_wzmemo")
  finish
endif
let g:loaded_outline_like_wzmemo = 1

コメントの通りです。お呪いの様なものだと思っておいてください。
何度読み込まれても問題ないものなら、必要ないと思いますが、思わぬことを引き起こすことがあるので、とりあえずつけておくのが安パイです。

この辺りの事は、こんな記事を読むよりも、「名無しのvim使い」様のような素晴らしいサイトを読めばバッチリです。

Cのヘッダファイルに書くインクルードガードのようなものです。
・・・と言うような書き方をすると、plugin 配下に置いたファイルがヘッダのようなもので autoload 配下に置いたものがCソースのような関係に見えるかもしれませんが、全然違います。

互換性オプションの副作用回避

let s:save_cpo = &cpo
set cpo&vim

これまたお呪いの様なものです。:h cpoptions参照ということで。
一言でいうと、"compatible-options (互換オプション)"の副作用を回避するためです。
vimrc とかで このオプションを変更している場合、上手く動作しないことがあるので、こうしておきます。

この辺りの事は、こんな記事を読むよりも、「名無しのvim使い」様のような素晴らしいサイトを読めばバッチリです。

ちなみに、&vimの意味は、:h cpoptionsを読むだけではわからないので、:h set-defaultを参照しましょう。

変数の初期化

let s:winid_ol = 1  "とりあえず1に?
let s:flg_switch = 0
let s:flg_leave = 0

s:winid_olは、ロケーションリストのWindow-IDを保持する変数です。
ロケーションリストを開いた時に設定するが、その前にイベントから呼ばれる関数内で使ってしまっていて、値がないと問題があることがあるので、とりあえずの値を入れてあります。
問題が行っていないので、1のままです。実はなにか不味いかもしれません。

s:flg_switchはカーソル同期のために使用しています。WinEnter イベント時に設定して、CursorMoved イベント時に判定しています。

  • WinEnter 時には、アウトライン表示のウィンドウに入った直後に、1を設定します。
  • CursorMoved 時には、1が設定されてよ場合に、アウトライン同期処理を行わないようにします。

こうすることで、明示的にアウトライン表示へ移動した場合以外、すなわちウィンドウコマンド(Ctrl-W)や、マウスクリックによってアウトラインのウィンドウが選択された瞬間に、アウトライン同期処理が暴発して、テキスト表示のカーソル位置がかわってしまうことをふせいでいま。す 

s:flg_leaveは、BufWinLeave イベント時設定して、WinEnter イベント時に判定しています。

  • BufWinLeave 時には、テキストのバッファが取り除かれようとしている場合に、1を設定します。
  • WinEnter 時には、1だったら、アウトラインを閉じます。

なんでこんな回りくどいことをしているのかというと、テキストが閉じられようとしている最中には、アウトラインを閉じることができなかったため、「テキストが閉じたよー」という情報を保持しておいて、他のウィンドウがアクティブになった瞬間に、アウトラインを閉じてあげる。という段取りを踏んでいるためです。

let s:treeTitle = 'Outline'
let s:treeIndent = 2

s:treeTitleは、アウトライン表示の下に表示されるタイトルです。実際には「[ロケーションリスト]Outline」となっています。

s:treeIndentは、アウトライン表示内で、レベルごとにインデントされる量です。

自動コマンドの設定

詳しくは、:h autocommandを読むべし。

autocmd.txtを読むと、今まで穏やかな物腰だった Vim ヘルプ様が、突如として曹殿のような口調に変化したことに戸惑いを覚えるかもしれない。しかも、いきなりガツンとこういう警告が書かれていて、ビビる人もいるかもしれない。

警告: 自動コマンドは大変強力であるので、思いも寄らない副作用をもたらすことがある。テキストを壊さないように注意しなければならない。

・・・各自、注意して使用するように!

au BufNewFile,BufRead *.txt setl fdm=expr fde=WzLikeOutline(v:lnum,'.',1)
au BufNewFile,BufRead *.md  setl fdm=expr fde=WzLikeOutline(v:lnum,'#',0)

テキストファイルの場合に、WzMemo 風の折り畳みを有効にしますよ('.')。インデントを許容しますよ(1)。という事です。
Markdown の場合には、#の数のレベルで折り畳みしますよ('#')。インデントは許容しませんよ(0)。ということです。

au BufNewFile,BufRead *.txt com! MOT call <SID>makeOutlineTree('.',1)
au BufNewFile,BufRead *.md  com! MOT call <SID>makeOutlineTree('#',0)
au BufNewFile,BufRead *.vim com! MOT call <SID>makeOutlineTree('',0)        "fdm=marker想定
au BufNewFile,BufRead *.sql com! MOT call <SID>makeOutlineTree('',0)        "fdm=marker想定

拡張子に応じて、起動用のユーザ定義コマンド(:MOT)を追加します。
ちなみにですが、ユーザ定義コマンドは先頭が大文字である必要があります。詳しくは:h E183を参照。

テキストファイルの場合は「非空白文字を除いた行頭の'.'」を目印にしてアウトラインを作成しますよ。という指定で、アウトライン作成用の関数を呼び出します。

Markdown の場合には、「行頭'#'」を目印にアウトライン作成しますよ、という(以下略)

.vim.sqlの場合は、マーカー折り畳みを想定しているので、第1引数は空で、第2引数は0です。
もし、マーカーで折り畳んでいる他の拡張子のファイルでもアウトライン表示したいなーと思ったら、随時ここに拡張子を付け足せばおk。

ちなみにですが、<SID>というのは、s:で定義したローカル関数やらを正しく呼び出すために必要な文字列です。

makeOutlineTreeって、引数が状況によって決まっていて、ユーザーが引数を指定して呼ぶよりも、:MOTを経由して読んだ方が確実じゃないですか。
だから、スクリプトローカルにしてあるんですけれども、そうすると、今度はユーザ定義コマンド内で call することもできなくなるんですよね。<SID>で、そういう状態を回避できるようです。

詳しくは、:h <SID>参照ということで。

MyFoldText()

偉大なる foldCC.vim がなければこの関数はありませんでした。
というか別に set foldtext=FoldCCtext()のままでも、それほど問題はありませんよ?
こちらは劣化版のようなものです。あちらはいろんな機能がありますし。

個人的に、set noetな人間なので、こんなものを作る羽目になりました。

WzLikeOutline()

foldexprに指定する関数です。
行頭の'.'の数を数えて、折り畳みの深さを求める関数になります。

一番最初に、これが欲しく作り始めたようなものです。せめて折り畳みだけでもいいから、Wzっぽくならないかなー、と。
書いてみたら意外と簡単にできて、テンションが上がったものです。

最初は vimrc にこの関数を直書きしていました。

s:makeOutlineTree()

次に書いたのがコレです。折り畳めたなら、アウトラインを出してみたくなるのがサガと言うものでしょう。
プラグインにしてみようと思ってからは、サクサクと出来上がりました。

s:makeFoldOutline()

アウトライン表示部分の折り畳みを作成する関数です。
ノード自体が増えてくると、アウトライン表示自体が見づらくなってきますから、あった方がいいかなと。
アウトライン表示用の foldtext 関数を書いた方がいいかもしれませんね。

s:getFoldLevelOutline()

上記のs:makeFoldOutline()の中から呼ばれている関数です。
アウトライン表示が何階層目のノードなのかを求めます。

s:incFoldLevel()

s:makeOutlineTree()すると定義されるマップに、z>z<があります。
※折り畳み方式が「式(EXPR)」の場合にのみ定義します。

このキー入力に対して、呼び出される関数です。

現在行を含むノードを起点にして、より階層の低いノードのまとまりに対して、レベルを増減させます。
作った本人もあんまり信用していません。動作後のアウトラインの状態はよく確認した方がよいです。

s:switch_outline()

s:makeOutlineTree()すると定義されるマップに、<Tab>があります。

TABキーが押された場合に、この関数が呼び出されるようにしてあります。

アウトライン表示と、テキストの間で、フォーカスをトグルします。

s:myWinEnter()

WinEnter イベント時に呼ばれるようにしてあります。
新しいウィンドウに入るということは、すなわち、TABキーによりフォーカスが変わったか、それ以外の理由で変わったかです。
TABキー以外でフォーカスが変わるということは、ウィンドウコマンド(Ctrl-W)か、マウス操作か、ほかのウィンドウが閉じた場合ということになります。

まぁ、そういうような条件で判断しつつ、アレコレしています。
詳しくはソースを見るべし。

s:outlineSync()

CursorMoved イベント時に呼ばれるようにしてあります。
カーソルが動いたときに、テキスト側とアウトライン側のフォーカスを同期させる処理が書いてあります。

このイベントではあまり時間のかかる処理をしない方がよいらしいです。
今の実装具合がどの程度なのか不安ですが、この文章を書いている限りでは、Celeron N3060 という非力 CPU でもつっかえたりするわけではないので、問題ないレベルだと信じたいと思います。

s:myBufWinLeave()

BufWinLeave イベント時に呼ばれるようにしてあります。名前が安直ですね。こういう名前しか付けられないのでスクリプトローカル関数がありがたいです。

テキストウィンドウが閉じたときに、自動的にアウトラインも閉じるための処理を記述してあります。

s:isOutllineList()

現在のウィンドウがアウトライン表示かどうかを判定する関数です。
いろんなところでこの判定を行うので関数化してあります。

最後に

今回 Vim プラグインを書くのも初めて、という人間が作ったものなので、実用性とかよりも、これからプラグイン書いてみたいなーという人が、勇気を持てればいいな、という程度の感覚です。

一応動いているので、自分では使いますけれどもね。

単純にテキストエディタとして Vim が好きでしたが、こんな簡単に機能追加したり、テキスト編集を自動化できるということが分かって、ますます好きになりました。

Denite + Defx で複数プロジェクトを渡り歩く

$
0
0

目的

複数のプロジェクトで作業をしないといけない場合、プロジェクトを切り替えながら渡り歩くのが結構めんどくさかったりします。
そういった場合にDenitedenite#custom#varを使うとプロジェクト間の行き来が少し楽になります。

環境

NVIM v0.5.0-94b7ff730
DeniteDefxが使えることが前提です。

詳細

:help denite#custom#var

設定

以下のように設定すると<Leader>pでプロジェクトの選択肢が表示され、Enterでプロジェクトルートが開きます。

lets:menus={}lets:menus.projects ={'description':'switch projects'}lets:menus.projects.command_candidates =[      \['project A','Defx ~/path/to/projectARoot'],      \['project B','Defx ~/path/to/projectBRoot'],      \['project C','Defx ~/path/to/projectCRoot'],      \]call denite#custom#var('menu','menus',s:menus)

nnoremap <silent><Leader>p:<C-u>Denite menu:projects<CR>

Defx以外のコマンドでも、command_candidatesに追加すれば選択式で使えるので結構便利そう。

Viewing all 5720 articles
Browse latest View live


<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>