Ruby で UTF-8 の文字列を SJIS で文字化けさせたり復元したりする
以下の記事を知人と話していたら思ったよりも盛り上がったので覚書。
ちなみにクイズのネタバレがあるので見たくない人は読まないでね!!
縺ゅ¢縺セ縺励※縺翫a縺ァ縺ィ縺
みたいな文字化けした文字列を Ruby で生成する
言及されている文字化けは『 UTF-8
な文字を SJIS
で表示すると文字化ける』っていうような話になります。
では、Ruby でこのような文字化けを行う場合どうするのがいいのでしょうか。
これは要するに『 UTF-8
を SJIS
として扱い UTF-8
で変換する』という処理で実現することができます。
まず、Ruby のデフォルトのエンコーディングは utf-8 なので単に文字列リテラルを定義した場合は utf-8
の文字列になります。
# 文字列リテラルは utf-8 utf8 = "やばたにえん" pp utf8.encoding # => #<Encoding:UTF-8>
次に String#force_encoding
を使って『内部のデータはそのままでエンコーディング情報のみ』を変更します。
NOTE: ちなみに #force_encoding
は #encode
と違い #valid_encoding
でチェックは行いません(thanks id:imaizumimr :)
utf8 = "やばたにえん" # 内部のエンコーディング情報のみ書き換える # 内部データはそのまま sjis = utf8.force_encoding(Encoding::SJIS) # エンコーディング情報のみ SJIS として扱われる # ちなみに SJIS は Windows-31J のエイリアス pp sjis.encoding # => #<Encoding:Windows-31J> # バイトコードは同じ pp utf8.bytes == sjis.bytes # => true
最後に『内部データは UTF-8
だけど文字コード情報 SJIS
』な文字列に対して UTF-8
でエンコードします。
この時に変換できない文字があるので無理やり変換されるように invalid: :replace, undef: :replace
オプションを指定しています。
utf8 = "やばたにえん" sjis = utf8.force_encoding(Encoding::SJIS) # SJIS だけど実データは UTF-8 な文字列を無理やり UTF-8 で変換させる pp sjis.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) # => "繧��縺溘↓縺医s"
これでいわゆる『文字化け』した文字列が生成できました。
文字化けしたコードを戻す
では、次は文字化けしたコードを復元してみましょう。
現状はこんな感じです。
utf8 = "やばたにえん" sjis = utf8.force_encoding(Encoding::SJIS) bake = sjis.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) pp bake # => "繧��縺溘↓縺医s" pp bake.encoding # => #<Encoding:UTF-8>
これを元に戻すのは比較的簡単で(全然簡単ではなかったけど…)先程の処理と逆のことをします。
utf8 = "やばたにえん" sjis = utf8.force_encoding(Encoding::SJIS) bake = sjis.encode(Encoding::UTF_8, invalid: :replace, undef: :replace) # まず SJIS でエンコードする。その後に文字コードを UTF-8 に指定する pp bake.encode(Encoding::SJIS, invalid: :replace, undef: :replace).force_encoding(Encoding::UTF_8) # => "\xE3\x82??たにえん"
これで復元することができます。
できるんですが文字化けさせるときのエンコードを無理やり行っているので上の手順で復元した場合は完璧に復元することはできません、うぐぅ…。
まとめ
文字コードなんもわからんが Ruby の String
は文字コード周りの実装がしっかりしててすごいなぁ。
普段書かないような処理なのでいろいろと知らない機能やオプションなんかの知見がしれてよかったです。
あと人とわいわいしながらコード書くのたのしいですね!!!
久々に書いてて楽しい Ruby のコードだった。
2021/01/21 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Bug #17530] irb handles << incorrectly with variable as identifier
irb
で以下のように入力したら意図しないエラーになったというバグ報告
irb(main):001:0' s1 = 'testing' = > "testing" [2021-01-20 23:11] irb(main):002:0' s2 = 'this' = > "this" irb(main):003:0" s2 <<s1 irb(main):004:0" adding text here does not work irb(main):005:0" s1 Traceback (most recent call last): 3: from /home/centos/.rubies/ruby-3.0.0/bin/irb:23:in `<main>' 2: from /home/centos/.rubies/ruby-3.0.0/bin/irb:23:in `load' 1: from /home/centos/.rubies/ruby-3.0.0/lib/ruby/gems/3.0.0/gems/irb-1.3.0/exe/irb:11:in `<top (required)>' SyntaxError ((irb):4: syntax error, unexpected local variable or method, expecting '(') adding text here does not work ^~~~ irb(main):006:0>
- 意図としては 3行目の
s2 <<s1
はs2.<<(s1)
のようにString#<<
を呼んでほしい - しかし、
irb
上ではヒアドキュメントと解釈されてしまっており 4〜5行目はヒアドキュメントの複数行として扱われてしまう - なんですが 5行目で Ruby のコードが実行される場合はヒアドキュメント
s2.<<(s1)
と解釈されてしまいおかしいことになっています- 4行目がそのまま Ruby のコードとして実行されている
- この問題の本質としては
s2 <<s1
というコードは『s2
という変数が定義されているかどうか』で意味が変わってしまう点です- 変数が定義されていれば
s2.<<(s1)
と解釈され、そうでなければヒアドキュメントとして扱われる
- 変数が定義されていれば
- なので 3行目のコードを解析する際に変数の有無も考慮する必要があるのですが irb の実装としては1行ずつコードを解析しておりその前の行で
s2
変数が定義されていてもs2 <<s1
がディアドキュメントとして解釈されてしまっている、って感じです - これ、直してみたいんだけどどう治すのがいいのかなあ…
[Bug #17547] Fix Ripper.lex("a <<b")
Ripper.lex("a <<b")
が意図する結果を返してなかったというバグ報告- 必要なパース結果が含まれていなかった
- これですが先程の [Bug #17530] を調べている時に見つけたバグで直せそうだったので直してパッチ投げました
- Ripper なので実装が難しいのかなあ、と思っていたんですが問題になっていた箇所は Ruby で書かれていたので比較的簡単でした
require "ripper" p Ripper.lex("a <<b") # 期待する挙動 => [[[1, 0], :on_ident, "a", CMDARG], [[1, 1], :on_sp, " ", CMDARG], [[1, 2], :on_heredoc_beg, "<<b", CMDARG]] # 実際の挙動 => [[[1, 2], :on_heredoc_beg, "<<b", CMDARG]]
- この修正は既にマージ済みです
[Bug #17556] ruby 2.7.2 ::YAML.dump ArgumentError: invalid value for Integer(): "20210101_"
YAML.dump '20210101_'
するとInteger
と解釈されてエラーになるというバグ報告
require "yaml" p YAML::VERSION # => "3.1.0" # error: `Integer': invalid value for Integer(): "20210101_" (ArgumentError) p YAML.dump '20210101_'
- このバグは既に修正済みで手元だと
YAML::VERSION
が3.2.0
では問題なく動作していました
require "yaml" p YAML::VERSION # => "3.2.0" p YAML.dump '20210101_' # => "--- '20210101_'\n"
[Bug #17554] [PATCH] Fix ObjectSpace.dump to include singleton class name
- 次のように
ObjectSpace.dump
に特異クラスを渡した場合に Ruby 3.0 だと"name"
が含まれなくなっているというバグ報告
require "objspace" puts ObjectSpace.dump(Object.new.singleton_class) # 2.7 => {"address":"0x55adae76b630", "type":"CLASS", "class":"0x55adae7a76d0", "name":"Object", "references":["0x55adae7a9250", "0x55adae76b720"], "memsize":464, "flags":{"wb_protected":true}} # 3.0 => {"address":"0x55d9048e80e0", "type":"CLASS", "class":"0x55d90476d738", "references":["0x55d90476e8b8", "0x55d9048e8158"], "memsize":472, "flags":{"wb_protected":true}}
- なるほどね?
[Bug #17423] Prepend
should prepend a module before the class
- Ruby 3.0 では継承リスト周りの処理が色々と改善された結果
#prepend
が意図する挙動をしていないケースがありました
module M; end module A; end class B; include A; end A.prepend M B.prepend M # B に prepend M しているのに反映されてないように見える p B.ancestors # 2.7 => [M, B, A, Object, Kernel, BasicObject] # 3.0 => [B, M, A, Object, Kernel, BasicObject]
- これの対処として
[M, B, M, A, Object, Kernel, BasicObject]
を返す提案がされました - この場合は
M
が重複してしまいますが、次のようなコードでも重複するので問題ないとの判断
module M; end class A; end class B<A; end A.prepend M B.prepend M # これは Ruby 3.0 以前からこのような挙動になっている p B.ancestors # => [M, B, M, A, Object, Kernel, BasicObject]
- この変更は既にマージされました
- 今後は以下のような挙動になる予定です
module M; end module A; end class B; include A; end A.prepend M B.prepend M p B.ancestors # 2.7 => [M, B, A, Object, Kernel, BasicObject] # 3.0 => [B, M, A, Object, Kernel, BasicObject] # 3.1 => [M, B, M, A, Object, Kernel, BasicObject]
- このあたりはちょっと注意して使う必要がありそうですねえ
2021/01/14 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Feature #17485] Keyword argument for timezone in Time.new
Time.new
でタイムゾーンを指定する場合、以下のように時間をすべて指定する必要がある
# OK Time.new(2021, 1, 1, 0, 0, 0, "+09:00") #=> ok: 2021-01-01 00:00:00 +0900 # これは意図するタイムゾーンを設定できない Time.new(2021, 1, 1, "+09:00") #=> bad: 2021-01-01 09:00:00 +0900 Time.new(2021, 1, "+09:00") #=> bad: 2021-01-09 00:00:00 +0900 Time.new(2021, "+09:00") #=> ArgumentError (mon out of range)
- キーワード引数
in
でタイムゾーンを設定できるようにするチケット
Time.new(2021, 1, 1, in: "+09:00") #=> ok: 2021-01-01 00:00:00 +0900 Time.new(2021, in: "+09:00") #=> ok: 2021-01-01 00:00:00 +0900
- 便利そう
- これはすでにマージ済み
[Feature #16806] Struct#initialize accepts keyword arguments too by default
Struct
のkeyword_init:
をデフォルトで有効化させるチケット
User = Struct.new(:name, :age) # これは以前の挙動のまま homu = User.new("homu", 14) # キーワード引数を渡すと keyword_init: true と同じように初期化される homu = User.new(name: "homu", age: 14)
- ただし、以下のようなケースで互換性が壊れるかもしれない
User = Struct.new(:name, :age) # 現状だと name に Hash オブジェクトが入ってしまうので既存の挙動と変わってしまう p User.new(name: "homu", age: 14) # => #<struct User name={:name=>"homu", :age=>14}, age=nil>
- この変更は 3.1 で警告を出すようにして 3.2 で対応される予定になる
[Bug #17519] set_visibility fails when a prepended module and a refinement both exist
- 以下のように
refine
後のメソッドを特異クラスを経由してprivate
化しようとするとエラーになるというバグ報告
module Nothing; end class X # prepend しなかったらエラーにはならない prepend Nothing def hoge end end # これは OK X.new.singleton_class.class_eval { private :hoge } module NeverUsed refine X do def hoge(*keys) end end end # `private': undefined method `hoge' for class `#<Class:#<X:0x0000558fa95b7d70>>' (NameError) # Refinements で拡張したあとに呼ぶとエラーになる X.new.singleton_class.class_eval { private :hoge }
- なにもわからない…
- これは Ruby 2.5, 2.6, 2.7 でも再現していた
[Bug #17533] Named capture is not assigned to the same variable as reserved words.
- 次のように正規表現で任意の変数に結果をキャプチャすることができる
# マッチした部分を result 変数に保存する /(?<result>\d+).*/ =~ "1234hoge" pp result # => "1234"
- この時に予約語をキーワード引数として定義している時に正しく代入されていなかった
def test(nil: :ng) # nil という変数名にマッチしたテキストが代入されるのを期待する # しかし、代入されない /(?<nil>\d+).*/ =~ "1234hoge" binding.local_variable_get(:nil) # => :ng end test
- この問題はすでに修正済み
def test(nil: :ng) /(?<nil>\d+).*/ =~ "1234hoge" binding.local_variable_get(:nil) # 3.0 => :ng # 3.1 => "1234" end test
[Bug #17534] Pattern-matching is broken with find pattern
- 次のような find パターンを含んだパターンマッチでぶっ壊れるという報告
case [1, 2, 3] in y puts "branch1" in [*, x, *] puts "branch2" else puts "branch3" end # output: __END__ -- raw disasm-------- trace: 1 0000 putnil ( 2) 0001 duparray <hidden> ( 1) 0003 dup ( 2) 0004 setlocal_WC_0 4 ( 2) 0006 jump <L002> ( 2) 0008 dup ( 4) 0009 topn 2 ( 4) 0011 opt_le <calldata:<=, 1> ( 4) 0013 branchunless <L013> ( 4) 0015 topn 3 ( 4) 0017 topn 1 ( 4) 0019 opt_aref <calldata:[], 1> ( 4) 0021 setlocal_WC_0 3 ( 4) 0023 jump <L012> ( 4) <L013> [sp: 2] 0025 pop ( 4) 0026 pop ( 4) * 0027 pop ( 4) 0028 jump <L006> ( 4) <L012> [sp: 2] 0030 pop ( 4) 0031 pop ( 4) 0032 pop ( 4) 0033 pop ( 4) 0034 jump <L004> ( 4) <L006> [sp: -1] 0036 pop ( 4) <L001> [sp: -1] 0037 pop ( 7) 0038 pop ( 7) trace: 1 0039 putself ( 7) 0040 putstring "branch3" ( 7) 0042 opt_send_without_block <calldata:puts, 1> ( 7) 0044 leave ( 7) <L002> [sp: 2] 0045 pop ( 2) 0046 pop ( 2) trace: 1 0047 putself ( 3) 0048 putstring "branch1" ( 3) 0050 opt_send_without_block <calldata:puts, 1> ( 3) 0052 leave ( 7) <L004> [sp: -1] 0053 pop ( 4) 0054 pop ( 4) trace: 1 0055 putself ( 5) 0056 putstring "branch2" ( 5) 0058 opt_send_without_block <calldata:puts, 1> ( 5) 0060 leave ( 7) --------------------- /tmp/vxdawqc/180:4: warning: Find pattern is experimental, and the behavior may change in future versions of Ruby! /tmp/vxdawqc/180:4: argument stack underflow (-1) /tmp/vxdawqc/180: compile error (SyntaxError)
- こんなエラーはじめてみた…
- 結構簡単に再現しそうなので怖い
[Feature #13683] Add strict Enumerable#single
- レシーバの要素を1つだけ返す
Enumerable#single
を追加する提案 - 似たようなメソッドに
Enumerable#first
があるがちょっと意味が違うEnumerable#first
: 先頭の要素を返す。見つからなかった場合はnil
を返すEnumerable#single
: 先頭の要素を返す。ただし、要素が2個以上、または存在しない場合は例外を発生させる
- あんまり利便性がわからないけど便利なのかな…?
- ちなみに ActiveSupport にも同様の PR が投げられてます
- https://github.com/rails/rails/pull/26206
- こっちは close されている
- 現状は
#single
という名前ではなくて違う名前の方がいいんじゃないか、みたいな名前付けの議論で止まってるぽい - と、思ってたら ActiveRecord で同じような機能の
ActiveRecord::FinderMethods#sole
というメソッドが追加されました- https://github.com/rails/rails/pull/40768
- こっちは
#sole
っていう名前になったぽい。別名で#find_by_sole
もあるみたいですが
- 更に ActiveSupport で
Enumerable#sole
を追加する PR もある- https://github.com/rails/rails/pull/40914
- こっちはまだマージされていない
- ActiveSupport で
Enumerable#sole
が追加されたあとに Ruby の標準に別名で同等の機能が入った場合、混乱しそうだなあ - ちなみにパターンマッチで取得するのはどうか、というコメントもされている
- https://bugs.ruby-lang.org/issues/13683#note-32
- 個人的にはこの書き方が好き
case []; in [a]; p a; end #=> NoMatchingPatternError ([]) case [1]; in [a]; p a; end #=> 1 case [1,2]; in [a]; p a; end #=> NoMatchingPatternError ([1, 2])
使ってる Vim プラグインを紹介する:コマンドの出力結果をバッファで表示する
気が向いたら書いていくシリーズ。
capture.vim でコマンドの出力結果をバッファで表示する
capture.vim
を使うとコマンドの出力結果をバッファ上で表示することができます。
使い方は簡単で :Capture 任意のコマンド
をするだけです。
:Capture echo "hello, world"
すれば hello, world
が新しいバッファに表示され、 :Capture map
すると設定したキーマッピングの一覧が新しいバッファに出力されます。
出力されるデータ量が多い場合にバッファ上で確認したりコピペしたりすることができるので便利です。
2021/01/07 今週の気になった bugs.ruby のチケット
内容は適当です。
今週と言っても今週みかけたチケットなだけでチケット自体は昔からあるやつもあります。
あくまでも『わたしが気になったチケット』で全ての bugs.ruby のチケットを載せているわけではありません。
[Feature #17471] send_if method for improved conditional chaining
- 以下のような
#send_if
を追加する提案
class Object def send_if(method, *args, proc: nil) yield(self) ? self.send(method, *args, &proc) : self end end
- これは以下のように『条件にマッチしたときのみメソッドチェーンを行う』ような場合に利用できる
puts 'Do you want a loud Merry Christmas? (y or I take it as a no)' answer = gets.chomp # 標準入力を受け取り、入力によって呼び出す処理を切り替える # 'y' が入力されたら大文字に変換して結合する puts %w(Merry Christmas).send(:map, &->(e) { answer == 'y' ? e.upcase : e }).join(' ') # 提案されている send_if ならこんな感じで記述できる puts %w(Merry Christmas).send_if(:map, proc: :upcase) { answer == 'y' }.join(' ')
- わたしもこういうのがほしいと思っていていろいろと考えているんですが今回の
#send_if
はかなりやってることが多くて個人的にはちょっと微妙- とはいえこうなってしまう理由もわかるのでうーーーーんって感じ
条件
と条件にマッチした場合の処理
の2つのブロックをメソッドに渡したいんですが Ruby でこのあたりどう表現すべきなのかが難しい
- ちなみに個人的には以下のような
tap + break
でいいじゃん、ってなっています
# Proposal puts %w(Merry Christmas).send_if(:map, proc: :upcase ) { answer == 'y' }.join(' ') # tap + break puts %w(Merry Christmas).tap { break _1.map(&:upcase) if answer == 'y' }.join(' ')
tap + break
に慣れてないとぎょっとするんですが、これはこれで Ruby としてみるとかなり素直なコードになっているので個人的には気に入っています- また似たようなチケットとしては以下のようなチケットもあるので気になる人はこちらも見てみると良いです
[Bug #17512] ostruct super regression
- 以下のように
super
を経由してOpenStruct
を参照した場合に Ruby 3.0 だと意図しない値が返ってきているというバグ報告
require 'ostruct' class Foo < OpenStruct def foo super end end p Foo.new(foo: 123).foo # Ruby 2.7 => 123 # Ruby 3.0 => nil
- この問題は
OpenStruct 0.3.2
で修正されている - 困っている人は個別に
OpenStruct
をインストールすると改善すると思います
[Misc #16436] hash missing #last method, make it not so consistent (it has #first)
Hash#first
はあるけどHash#last
はないので一貫性がないよねーっていうチケット
homu = { id: 1, name: "homu", age: 14 } pp homu.first # => [:id, 1] # error: undefined method `last' for {:id=>1, :name=>"homu", :age=>14}:Hash (NoMethodError) pp homu.last
- これは
Hash#first
はEnumerable#first
で実装されておりEnumerable#last
がないからですEnumerable#first
があってEnumerable#last
がないのは知らなかった- まあ確かにイテレーションを考えると
#first
はあっても#last
がないのはなんとなくわかるよな…?
- ちなみに以下のように
#reverse_each
を使うと終端の要素を取得することはできます
homu = { id: 1, name: "homu", age: 14 } pp homu.reverse_each.first # => [:age, 14]
- このチケット自体には強い気持ちでの要望とかはないので、もし必要な人とかがいればコメントしてみるといいと思います
[Bug #17488] Regression in Ruby 3: Hash#key? is non-deterministic when argument uses DelegateClass
- 次のように Ruby 3.0 で
Hask#key?
にDelegateClass
を渡すと意図しない結果が返ってくるというバグ報告- Ruby 3.0 では
Hask#key?
の結果が起動毎に変わることがある
- Ruby 3.0 では
puts "Running on Ruby: #{RUBY_DESCRIPTION}" program = <<~EOS require "delegate" TypeName = DelegateClass(String) hash = { "Int" => true, "Float" => true, "String" => true, "Boolean" => true, "WidgetFilter" => true, "WidgetAggregation" => true, "WidgetEdge" => true, "WidgetSortOrder" => true, "WidgetGrouping" => true, } puts hash.key?(TypeName.new("WidgetAggregation")) EOS iterations = 20 results = iterations.times.map { `ruby -e '#{program}'`.chomp }.tally # Ruby 3.0 で実行すると false が返ってくることがある puts "Results of checking `Hash#key?` #{iterations} times: #{results.inspect}" # Ruby 2.7 => Results of checking `Hash#key?` 20 times: {"true"=>20} # Ruby 3.0 => Results of checking `Hash#key?` 20 times: {"false"=>12, "true"=>8}
- この問題は開発版ではすでに修正済みです
[Bug #17481] Keyword arguments change value after calling super without arguments in Ruby 3.0
- 次のように
super
を呼び出す前と後でキーワード引数の値が変わってしまうというバグ報告
class BaseTest def call(a:, b:, **) end end class Test < BaseTest def call(a:, b:, **options) p options # => {:c=>{}} super # super を呼び出した後で options の値が変わってしまっている… p options # => {:c=>{}, :a=>1, :b=>2} end end Test.new.call(a: 1, b: 2, c: {})
- どうして…どうして…
- この問題は
Ruby 3.0
で再現しており開発版ではすでに修正済みです
[Feature #17472] HashWithIndifferentAccess like Hash extension
ActiveSupport::HashWithIndifferentAccess
をサポートする機能を Ruby で実装する?というチケット- ちょっとチケットの意図が読み取れてないので間違ってるかもしれませんが『Ruby 本体で
ActiveSupport::HashWithIndifferentAccess
を実装する(提供する)』というわけではなくて『`ActiveSupport::HashWithIndifferentAccess
を高速化できる仕組みを Ruby 側で用意する』っていうのが趣旨なのかな? - 具体的に言うと Ruby 3.0 で追加された
Symbol#name
みたいなのを Ruby 本体でActiveSupport::HashWithIndifferentAccess
を高速化できるような機能を提供する感じなんですかね- コメント読んでる限りこのあたりちょっと認識のズレがありそう
- 最初読んだ時に『
ActiveSupport::HashWithIndifferentAccess
を Ruby で提供するのはやめてくれ〜〜〜』と思っていたんですが機能提供って意味だとかなりよさそうですね
[RP] Remove deprecated URI.escape/URI.unescape
- 長らく非推奨だった
URI.escape
/URI.unescape
が Ruby 3.0 から削除された - 今後は代替として
ERB::Util.#url_encode
やCGI.escape
などを利用していく必要がある - 参照
使ってる Vim プラグインを紹介する:行番号を指定してファイルを開く
気が向いたら書いていくシリーズ。 よく考えたらアドベントカレンダーで書けばよかったのでは…?
行番号を指定してファイルを開く file-line
このプラグインを入れると vim
コマンドでファイルを開くときに
$ vim index.html:20
のように行番号を指定して開くことができるようになります。
また vim
コマンドだけではなくて『 vim
のコマンド』でも行番号を指定して開けます。
:edit index.html:20 # 新しいタブで開く :tabnew index.html:20
エラー出力のフォーマットは ファイル名:行番号
みたいになっていることが多いので、エラー出力をそのままコピペすればエラー位置を一発で開くことができるので便利。
使ってる Vim プラグインを紹介する:カーソル下の単語をハイライトする
気が向いたら書いていくシリーズ。
t9md/vim-quickhl
カーソル下の単語をハイライトするプラグインです。
ハイライトされた単語は表示されているウィンドウすべてに反映されます。
# スペース + m でカーソル下の単語をハイライトする nmap <Space>m <Plug>(quickhl-manual-this) # 選択したテキストをハイライトする xmap <Space>m <Plug>(quickhl-manual-this) # スペース + m ですべてのハイライトを無効にする nmap <Space>M <Plug>(quickhl-manual-reset) # operator として定義しておく # mi( でカッコの中をハイライトする等 nmap m <Plug>(operator-quickhl-manual-this-motion) vmap m <Plug>(operator-quickhl-manual-this-motion)
こんな感じで変数がどこで使われているのかが視覚的にわかるようになるので便利。