物置き

指の負荷を軽減しようとしての迷走

smartchr.vim導入以来、
快適に入力するためにi(nore)?map周りの設定をいろいろ変更していて、
1キーでいかに複数の文字を入力するか、
というアプローチでいろいろ考えていたんだけど、
こちらのエントリをみて目から鱗が落ちた。


http://ujihisa.nowa.jp/entry/155f135406


押しやすいキーの組み合わせの2ストロークで、
単一の文字を入力することによって、指の負荷を減らす(=FPを節約)、
というのもありなのか!なるほど!!
i(nore)?mapでの複数ストロークによる入力なんて考えもしなかったす。


ということで、このアプローチを真似させていただくことにしますた。


元エントリだと、:をプレフィックスキー的につかっているけど、
自分としてはもうちょっと押しやすいキーがいいと思った。


で、なにがいいかを考えたところ、
Jキーは一等地にあるくせに、テキスト入力の頻度が少ないような気がした。
(キー配列はQWERTY)

ただまあ、なんとなくでやるのも何なので、
過去に自分が書いたソースコードで、各文字がどれくらい使われているのかを計測してみることにした。
こんな感じのスクリプトを書いて、自分のソースコードを食わせてみた。

# encoding: cp932
# 使い方: <スクリプトファイル> [カウント対象ファイルが置かれたディレクトリ]...
# パラメータを指定しなかったら、カレントディレクトリを再帰的に集計
import os, sys

# 検出対象ディレクトリ
dirs = sys.argv[1:]
if len(dirs) == 0: dirs.append(os.path.abspath("."))
# 処理対象ファイル拡張子
targetext = [ '.c', '.cpp', '.h' ]
#  再帰的に処理するか?
recurse = True
# エンコード方法
fencode = 'shift_jis'
# 除外パターン
exclude_patterns = [
	''
]

# 指定したキャラクタが集計対象文字かどうかを判定
def isTargetChr(c): return c.isalnum() or c in ';:+-*/=<>#,.?{}[]()&#"\'!%~^|_ \t'

# キャラクタ毎の出現数を集計
chrMap = [0] * 256
for d in dirs:
	files = os.listdir(d)
	for f in files:
		fullPath = os.path.join(d, f)
		if os.path.isdir(fullPath):
			if recurse: dirs.append(fullPath)
			continue
		if os.path.splitext(f)[1] not in targetext: continue

		try: fIn = open(fullPath)
		except: continue
		print "counting: " + fullPath

		for line in fIn:
			for pat in exclude_patterns:
				n = line.find(pat)
				if n != -1: line = line.replace(pat, '')
			# 一旦Unicode変換することで、全角文字を人文字として処理できるように
			for c in unicode(line, fencode):
				c = c.encode(fencode)
				if isTargetChr(c): chrMap[ord(c)] += 1

# 結果の出力
for i, count in enumerate(chrMap):
	if isTargetChr(chr(i)): print "%s : %d" % (chr(i), count)


その結果、下記のような結果が得られた。ちなみに、元ソースはC++
(他の言語だと状況がかわってくるかも、Javaとかはもうちょっと'J'の活躍の機会が多そうw
あと、書く人によっても違うかも知れないし。)

325488
e 275193
t 250354
* 244387
r 172858
n 171743
i 156436
a 146553
o 119466
s 111291
l 95709
) 94625
( 94622
C 85812
S 81605
c 80389
p 80361
d 76729
/ 76562
m 73286
; 72619
T 61704
u 60118
_ 59126
I 56677
L 55340
E 51776
D 50289
P 48793
R 48471
f 47909
O 40381
= 39246
V 38371
A 37399
, 37242
: 36399
. 33032
N 32450
h 31328
y 30011
G 29894
g 29695
x 25012
F 24849
M 23585
b 23451
B 22362
} 20964
{ 20963
U 20763
w 20688
20230
v 19666
0 18846
- 18782
> 17774
& 14298
! 12237
z 11640
W 11052
1 10712
" 9967
2 8539
H 8236
k 7745
# 7283
[ 7103
] 7103
+ 6979
6553
Y 5863
X 5632
3 4135
5 3897
4 3285
6 3110
K 2975
j 2474
2299
9 2255
8 2243
7 1885
% 1342
~ 1063
Z 1054
q 829
' 557
? 495
Q 448
J 424
^ 29


'*'とかが突出しているのはたぶん、コメントのせい。
(コメント定型文で「******」みたいのがいっぱいある。)


しかし、やっばJキーはあまり活用されてねえー。
英字だけでみると、小文字ではqに次いでワースト2、大文字に至ってはワースト1じゃないか。
一等地にいるくせになんてやつだ。けしからん。


ということで、自分はJキーをプレフィックスキーとして
上のテーブルの上位にある、押しにくいキーを割り当てていくことにした。

その際、jに続く文字には母音を除外し、
jj..と連続するのも避けるようにして割り振ってみたら、
こんな感じのマッピングになった。

imap <buffer> jdj *
imap <buffer> jdd /
imap <buffer> jde %
inoremap <buffer><expr> jg Closebracket('()[]{}', ['cString','cCppString','cCharacter', 'cCommentL', 'cComment'])
imap <buffer> jff (
imap <buffer> jk =
imap <buffer> jt _
imap <buffer> jv :
imap <buffer> jfg {
imap <buffer> jdm -
imap <buffer> jcc >
imap <buffer> jr &
imap <buffer> jn !
imap <buffer> jw "
imap <buffer> jq '
imap <buffer> jdp +
imap <buffer> jcj <
imap <buffer> jfd [
imap <buffer> jx <bar>


3ストローク入力とかはちょっと煩雑かも。
この辺は改良の余地ありまくりの予感。
3ストロークにするくらいなら、Kあたりのキーもプレフィックスキーとして使って、2ストロークの組み合わせ数を増やすほうがいいかもしれない。


しかし、元エントリにある「直感的操作感」なんてあったもんじゃない。これはひどいw


たまに、このマッピングのせいで困ることがあるかもしれないけど、
そんときはC-Vでマッピングを回避するということで。


jgにマッピングしているClosebracketという関数は、
直前のテキストを調べて、対応する閉じ括弧を選択する、というもの。
対応する')'と'}'と']'を調べて、もっとも内側の括弧を閉じることで、
ひとつのキーで括弧閉じを済まそうという魂胆。


こんな感じの関数

function! Closebracket(brackets, ignore_syntaxs)
  let ignore_syntax_names = {}
  for x in a:ignore_syntaxs
    let ignore_syntax_names[x] = 1
  endfor
  " 現在位置を保持しておく
  let [ cur_l,cur_c ] = [ line('.'),col('.')]
  let org_lz = &lz
  let &lz=1

  " 各括弧のカウント変数
  let search_pattern = '\V'
  let chr_dict_index = {}
  let counts = []
  let close_chr = []
  for x in range(0, len(a:brackets)-1)
    let chr = a:brackets[x]
    let chr_dict_index[chr] = [x/2, x%2]
    if x%2 == 0 | let counts += [1] | endif
    if x%2 != 0 | let close_chr += [ chr ] | endif

    if x != 0
      let search_pattern .= '\|'
    endif
    let search_pattern .= chr
  endfor
  while(1)
    " 1.検索する
    let pos = searchpos(search_pattern,'bW')
    if pos == [0,0]
      break
    endif
    " 2.見つかった場合、その位置のシンタックスをしらべる。
    " もし、文字列、文字、コメント文字列なら、再度検索を実行する。
    let syn_name = synIDattr(synID(line("."),col("."),1), "name")
    if get(ignore_syntax_names, syn_name, 0) != 0
      continue
    endif
    " 3.現在位置の文字を取得 getline(".")[col(".")-1]
    let chr = getline(".")[col(".")-1]
    " 4.検索語の位置のキャラクタに応じてカウントを変更。
    " 変更した結果、値が0になったら、その文字を返す。
    let index = chr_dict_index[chr][0]
    if chr_dict_index[chr][1] == 0
      let counts[index] = counts[index]-1
    else
      let counts[index] = counts[index]+1
    endif

    if counts[index]==0
      " 終了するまえに、カーソルを元の位置に戻すことを忘れないこと
      call cursor(cur_l,cur_c)
      let &lz=org_lz
      return close_chr[index]
    endif
  endwhile

  call cursor(cur_l, cur_c)
    let &lz=org_lz
  return ''
endfunction

ちょっと使ってみた感じ、慣れることさえできれば、指の負荷はだいぶ軽減されそうな予感。
しばらく使って、問題なくつかえるようであれば、C/C++以外のファイルタイプにも適用しよう。