Skip to content

Instantly share code, notes, and snippets.

@kiwanami
Created May 30, 2011 02:27
Show Gist options
  • Select an option

  • Save kiwanami/998378 to your computer and use it in GitHub Desktop.

Select an option

Save kiwanami/998378 to your computer and use it in GitHub Desktop.
Modeless Japanse input method on IIIMCF for Emacs
;;; iiimcf-yc.el --- yc emulation extension for iiimcf
;; !!! EXPERIMENTAL !!!
;; Copyright (C) 2011 SAKURAI Masashi
;; Author: SAKURAI Masashi <m.sakurai at kiwanami.net>
;; Keywords: languages, tools
;; This program is free software; you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; IIIMCF で boild-egg, YC のようなモードレス変換機能を追加します。
;; boild-egg : http://usir.kobe-c.ac.jp/boiled-egg/
;; YC : http://www.ceres.dti.ne.jp/~knak/yc.html
;; AYC 有効中は入力文字のローマ字変換プレビューが出ますので、
;; すぐに打ち間違いに気づくことが出来ます。
;; auto-complete と連携して積極的に補完できます。
;;; Code:
(eval-when-compile
(require 'cl))
;;==================================================
;; Customize
(defvar ayc-splitter " \t\n\r\""
"変換の範囲を特定する区切り文字")
(defgroup ayc nil
"Yet Another YC preview"
:group 'mule
:prefix "ayc-")
(defface ayc-preview-face
'((t (:background "khaki1" :foreground "black")))
"プレビューのface"
:group 'ayc)
(defface ayc-preview-wrong-face
'((t (:background "MistyRose" :foreground "DimGray")))
"非日本語のプレビューのface"
:group 'ayc)
(defvar ayc-auto-adjust-space 'insert
"日本語と英単語のスペースの調整方法。
'insert : 変換しない単語の後でスペースを入力した後、自動でスペースを単語の前にも入れる。
'delete : 変換した後、一つ前の不必要なスペースを詰める。
nil : なにもしない。")
(defvar ayc-cursor-color "Hotpink1" "AYC モードのカーソールの色。")
(defvar ayc-romaji-kana-table
'(( "a" . "あ") ( "i" . "い") ( "u" . "う") ( "e" . "え") ( "o" . "お")
("ka" . "か") ("ki" . "き") ("ku" . "く") ("ke" . "け") ("ko" . "こ")
("sa" . "さ") ("si" . "し") ("su" . "す") ("se" . "せ") ("so" . "そ")
("ta" . "た") ("ti" . "ち") ("tu" . "つ") ("te" . "て") ("to" . "と")
("na" . "な") ("ni" . "に") ("nu" . "ぬ") ("ne" . "ね") ("no" . "の")
("ha" . "は") ("hi" . "ひ") ("hu" . "ふ") ("he" . "へ") ("ho" . "ほ")
("ma" . "ま") ("mi" . "み") ("mu" . "む") ("me" . "め") ("mo" . "も")
("ya" . "や") ("yu" . "ゆ") ("yo" . "よ")
("ra" . "ら") ("ri" . "り") ("ru" . "る") ("re" . "れ") ("ro" . "ろ")
("la" . "ら") ("li" . "り") ("lu" . "る") ("le" . "れ") ("lo" . "ろ")
("wa" . "わ") ("wi" . "ゐ") ("wu" . "う") ("we" . "ゑ") ("wo" . "を")
("n'" . "ん") ( "n" . "ん") ("m'" . "ん") ( "m" . "ん")
("ga" . "が") ("gi" . "ぎ") ("gu" . "ぐ") ("ge" . "げ") ("go" . "ご")
("za" . "ざ") ("zi" . "じ") ("zu" . "ず") ("ze" . "ぜ") ("zo" . "ぞ")
("da" . "だ") ("di" . "ぢ") ("du" . "づ") ("de" . "で") ("do" . "ど")
("ba" . "ば") ("bi" . "び") ("bu" . "ぶ") ("be" . "べ") ("bo" . "ぼ")
("pa" . "ぱ") ("pi" . "ぴ") ("pu" . "ぷ") ("pe" . "ぺ") ("po" . "ぽ")
("kya" . "きゃ") ("kyu" . "きゅ") ("kye" . "きぇ") ("kyo" . "きょ")
("sya" . "しゃ") ("syu" . "しゅ") ("sye" . "しぇ") ("syo" . "しょ")
("sha" . "しゃ") ("shu" . "しゅ") ("she" . "しぇ") ("sho" . "しょ")
("cha" . "ちゃ") ("chu" . "ちゅ") ("che" . "ちぇ") ("cho" . "ちょ")
("tya" . "ちゃ") ("tyu" . "ちゅ") ("tye" . "ちぇ") ("tyo" . "ちょ")
("nya" . "にゃ") ("nyu" . "にゅ") ("nye" . "にぇ") ("nyo" . "にょ")
("hya" . "ひゃ") ("hyu" . "ひゅ") ("hye" . "ひぇ") ("hyo" . "ひょ")
("mya" . "みゃ") ("myu" . "みゅ") ("mye" . "みぇ") ("myo" . "みょ")
("rya" . "りゃ") ("ryu" . "りゅ") ("rye" . "りぇ") ("ryo" . "りょ")
("lya" . "りゃ") ("lyu" . "りゅ") ("lye" . "りぇ") ("lyo" . "りょ")
("gya" . "ぎゃ") ("gyu" . "ぎゅ") ("gye" . "ぎぇ") ("gyo" . "ぎょ")
("zya" . "じゃ") ("zyu" . "じゅ") ("zye" . "じぇ") ("zyo" . "じょ")
("jya" . "じゃ") ("jyu" . "じゅ") ("jye" . "じぇ") ("jyo" . "じょ")
( "ja" . "じゃ") ( "ju" . "じゅ") ( "je" . "じぇ") ( "jo" . "じょ")
("bya" . "びゃ") ("byu" . "びゅ") ("bye" . "びぇ") ("byo" . "びょ")
("pya" . "ぴゃ") ("pyu" . "ぴゅ") ("pye" . "ぴぇ") ("pyo" . "ぴょ")
("kwa" . "くゎ") ("kwi" . "くぃ") ("kwe" . "くぇ") ("kwo" . "くぉ")
("tsa" . "つぁ") ("tsi" . "つぃ") ("tse" . "つぇ") ("tso" . "つぉ")
( "fa" . "ふぁ") ( "fi" . "ふぃ") ( "fe" . "ふぇ") ( "fo" . "ふぉ")
("gwa" . "ぐゎ") ("gwi" . "ぐぃ") ("gwe" . "ぐぇ") ("gwo" . "ぐぉ")
("dyi" . "でぃ") ("dyu" . "どぅ") ("dye" . "でぇ") ("dyo" . "どぉ")
("dhi" . "でぃ") ("dhu" . "どぅ") ("dhe" . "でぇ") ("dho" . "どぉ")
("shi" . "し") ("tyi" . "てぃ") ("thi" . "てぃ") ("chi" . "ち") ("tsu" . "つ") ("ji" . "じ")
( "fu" . "ふ") ( "ye" . "いぇ")
("va" . "ヴぁ") ("vi" . "ヴぃ") ("vu" . "ヴ") ("ve" . "ヴぇ") ("vo" . "ヴぉ")
( "xa" . "ぁ") ( "xi" . "ぃ") ( "xu" . "ぅ") ( "xe" . "ぇ") ( "xo" . "ぉ")
("xtu" . "っ") ("xya" . "ゃ") ("xyu" . "ゅ") ("xyo" . "ょ") ("xwa" . "ゎ")
("xka" . "ヵ") ("xke" . "ヶ")
("1" . "1") ("2" . "2") ("3" . "3") ("4" . "4") ("5" . "5")
("6" . "6") ("7" . "7") ("8" . "8") ("9" . "9") ("0" . "0")
("!" . "!") ("@" . "@") ("#" . "#") ("$" . "$") ("%" . "%")
("^" . "^") ("&" . "&") ("*" . "*") ("(" . "(") (")" . ")")
("-" . "ー") ("=" . "=") ("`" . "`") ("\\" . "¥") ("|" . "|")
("_" . "_") ("+" . "+") ("~" . " ̄") ("[" . "「") ("]" . "」")
("{" . "{") ("}" . "}") (":" . ":") (";" . ";") ("\"" . "”")
("'" . "’") ("." . "。") ("," . "、") ("<" . "<") (">" . ">")
("?" . "?") ("/" . "/")
))
;;==================================================
;; Utilities
(eval-and-compile
(defvar ayc-debug nil "Debug output switch.")) ; debug
(defvar ayc-debug-count 0 "[internal] Debug output counter.") ; debug
;; (setq ayc-debug t)
(defun ayc-log-func (&rest args)
"[internal] Debug output"
(with-current-buffer (get-buffer-create "*ayc-debug*")
(save-excursion
(goto-char (point-max))
(insert (format "%5i %s\n" ayc-debug-count (apply 'format args)))))
(incf ayc-debug-count))
(defmacro ayc-log (&rest args)
"[internal] Debug macro."
(when ayc-debug
(let ((syms (loop for i in args collect (gensym))))
`(progn
(let ( ,@(loop for s in syms
for v in args
collect (list s v)) )
(ayc-log-func ,@syms))))))
(defun ayc-message-mark ()
"[internal] Debug marker."
(interactive)
(ayc-log "==================== mark ==== %s"
(format-time-string "%H:%M:%S" (current-time))))
(defun ayc-pop-to-log-buffer ()
"[internal] Display log buffer."
(interactive)
(let ((buf (get-buffer "*ayc-debug*")))
(when buf
(pop-to-buffer buf)
(set-window-point
(get-buffer-window buf)
(with-current-buffer buf (point-max))))))
;; (global-set-key (kbd "M-j") 'ayc-pop-to-log-buffer)
;;==================================================
;; Romaji translation
(defvar ayc-romaji-regexp
(regexp-opt
(nconc (mapcar 'car ayc-romaji-kana-table)
(mapcar (lambda (x)
(let ((romaji (car x)))
(if (string-match "^\\([kgszjcftdhbpmrwv]\\)" romaji)
(concat (list (elt romaji 0)) (car x)))))
ayc-romaji-kana-table)))
"ローマ字を拾う正規表現。 ayc-romaji-kana-table を更新したらこの変数も更新すること。")
(defun ayc-romaji-to-kana (string)
"ローマ字からひらがなに変換する。"
(with-temp-buffer
(insert string)
(goto-char (point-min))
(while (re-search-forward ayc-romaji-regexp nil t)
(let ((match (match-string 0)))
(replace-match
(concat (when (and (> (length match) 1)
(equal (elt match 0) (elt match 1))
(not (equal (elt match 0) ?n)))
(setq match (substring match 1))
"っ") ;
(cdr (assoc match ayc-romaji-kana-table))))))
(goto-char (point-min))
(while (re-search-forward "んん" nil t)
(replace-match "ん"))
(buffer-string)))
(defun ayc-romaji-to-kana-test ()
(interactive)
(pop-to-buffer "*atok-romaji-to-kana-test*")
(erase-buffer)
(loop for (in out) in
'(("kyouhaiitenkidesune" "きょうはいいてんきですね")
("kyouhane" "きょうはね")
("kyouhanne" "きょうはんね")
("mikanndesu" "みかんです")
("mikandesu" "みかんです")
("neko" "ねこ")
("nyann" "にゃん")
("nyan" "にゃん")
("sekkaku" "せっかく")
("toukyoutokkyokyokakyoku" "とうきょうとっきょきょかきょく")
("1asdfnkm" "1あsdfんkん"))
for rs = (ayc-romaji-to-kana in)
do
(insert (format "%s : %s / %s <= %s\n"
(if (equal rs out) "OK" "NG")
rs out in))))
;; (ayc-romaji-to-kana-test)
(defun ayc-collect-word ()
"変換すべき単語開始位置を拾う。見つからない場合は現在位置が返る。"
(save-excursion
(let ((poss (re-search-backward
(concat "\\([" ayc-splitter "]\\|[[:multibyte:]]\\)") nil t)))
(if poss (incf poss)
(setq poss (or (re-search-backward "^" nil t) (point-min))))
poss)))
(defun ayc-input-at-point (&optional delete)
"point 位置のローマ字を取得する。DELETE が指定してあれば、ローマ字を削除する。"
(let ((poss (ayc-collect-word)) (pose (point)) str)
(setq str (and (< poss pose) (buffer-substring-no-properties poss pose)))
(when (and str delete)
(cond
((ayc-preview-check (ayc-romaji-to-kana (downcase str)))
;; 変換するとき
(delete-region poss pose)
(ayc-auto-adjust-space-delete (point)))
(t
;; 変換しないとき
(ayc-auto-adjust-space-insert poss)
(setq str nil))))
str))
(defun ayc-auto-adjust-space-delete (pos)
"POS の位置のスペースを自動で削除する。"
(when (eq ayc-auto-adjust-space 'delete)
(save-excursion
(goto-char pos)
(when (and (not (bolp))
(eql (char-before) ?\ ))
(delete-backward-char 1)))))
(defun ayc-auto-adjust-space-insert (pos)
"POS の位置に自動でスペースを挿入する。"
(when (eq ayc-auto-adjust-space 'insert)
(save-excursion
(goto-char poss)
(when (and (not (bolp))
(not (string-match
(concat "[" ayc-splitter "]")
(char-to-string (char-before)))))
(insert " ")))))
(defvar ayc-last-invoked-string nil
"変換しようとした元の文字列。
ayc-invoke で記録されて ayc-inactive-hook, ayc-commit-hook で削除される。")
(make-variable-buffer-local 'ayc-last-invoked-string)
(defun ayc-invoke (&optional cmds)
"ローマ字を拾って変換する。その際、変換した後で引数のキー入力を追加する。"
(unless current-input-method
(let ((str (ayc-input-at-point t)))
(ayc-log "invoke: %S" str)
(cond
(str
(ayc-preview-stop)
(setq ayc-last-invoked-string str)
(call-interactively 'toggle-input-method)
(setq unread-command-events
(append unread-command-events
(listify-key-sequence str) cmds))
(ayc-log "invoke: unread %S" unread-command-events))
(t (ayc-fallback-command this-command)))
str)))
(defun ayc-fallback-command (&optional except-command)
"ayc-minor-mode を off にして本来のコマンドを実行する"
(let* ((ayc-minor-mode nil)
(keys (this-command-keys-vector))
(command (if keys (key-binding keys))))
(when (and (commandp command)
(not (eq command except-command)))
(setq this-command command)
(call-interactively command))))
;;==================================================
;; Preview
(defvar ayc-preview-popup nil "popup インスタンス保存用")
(make-variable-buffer-local 'ayc-preview-popup)
(defun ayc-preview-check (str)
"アルファベットが含まれてなければ t を返す。"
(> 1 (loop for i across str
count (and (<= 32 i) (< i 128)))))
(defun ayc-preview-spell-check (str)
"スペルチェッカーが文句を言わない単語を含んでいれば t を返す。"
(with-temp-buffer
(erase-buffer)
(call-process-shell-command (format "echo '%s' | aspell -a" str) nil t)
(goto-char (point-min))
(if (re-search-forward "^\\*" nil t) t nil)))
(defun ayc-pre-command-hook ()
"[hack] カーソールの色を元に戻すなど。"
(ayc-revert-cursor-color))
(defun ayc-get-current-cursor-color ()
"現在のカーソールの色を取得する"
(frame-parameter (selected-frame) 'cursor-color))
(defun ayc-revert-cursor-color ()
"カーソールの色を変える"
(cond
(ayc-minor-mode ; ayc ON の時
(cond
((equal ayc-cursor-color (ayc-get-current-cursor-color))) ;; 期待通り 何もしない
(t ;; 期待と違うのでカーソールの色を変える
(set-cursor-color ayc-cursor-color)))) ;; backup は取ってるはずなので変えるだけ
(t ; ayc OFF の時
(cond
((equal ayc-cursor-color (ayc-get-current-cursor-color)) ;; この色と違うはず
(set-cursor-color (or ayc-cursor-color-backup
ayc-cursor-color-backup-default)))
(t )));; 多分何もしなくて大丈夫のはず
))
(defun ayc-post-command-hook ()
"[hack] ここでリアルタイムにひらがなのプレビューを表示させる。"
(ayc-preview-show))
(defun ayc-preview-show ()
"ひらがなのプレビューを表示させる。"
(ignore-errors
;; まず以前のポップアップを消す
(when (and ayc-preview-popup (popup-p ayc-preview-popup))
(popup-delete ayc-preview-popup)
(setq ayc-preview-popup nil))
;; プレビューネタを取得する
(let* ((data (ayc-input-at-point))
(kana (ayc-romaji-to-kana (downcase data)))
(check (ayc-preview-check kana)))
(unless check ;; ひらがなでなかった場合
(setq kana
(if (ayc-preview-spell-check data) "" ;; 英単語が含まれる場合は無視
(concat kana "★")))) ;; ミスタイプっぽかった場合は注意マークを追加する
(when (and (< 0 (length kana))
(not (and (ac-menu-live-p) ; auto-complete が出てないとき
(not (popup-hidden-p ac-menu)))))
;; プレビューを表示する
(let ((win-width (window-width))
(win-height (window-height))
(line-start (line-beginning-position))
(popup-width (string-width kana))
(popup-height 1) (popup-pos (- (point) (length data))))
(when (< 1 (count-screen-lines line-start (point)))
;; 複数行の時は fill-column で改行させる
;; また、行を跨ぐときには邪魔にならないところに表示させる
(let ((fill-column (- win-width 4)))
(setq kana
(with-temp-buffer
(insert kana)
(fill-paragraph)
(setq popup-height
(count-lines (point-min) (point-max))
popup-width (- win-width 4))
(buffer-string))))
(save-excursion
(cond
((< (/ (* win-height 3) 4)
(count-screen-lines (window-start) (point)))
;; 上側に表示する
(goto-char (line-beginning-position))
(vertical-motion (- -1 popup-height))
(setq popup-pos (point)))
(t
;; 下側に表示する
(goto-char (1+ (line-end-position)))
(vertical-motion -1)
(setq popup-pos (point))))))
(setq ayc-preview-popup
(popup-create
popup-pos popup-width popup-height
:around t :face
(if (ayc-preview-check kana)
'ayc-preview-face 'ayc-preview-wrong-face)))
(popup-set-list ayc-preview-popup (append (split-string kana "\n")))
(popup-draw ayc-preview-popup))))))
(defun ayc-preview-reposition ()
"[hack] auto-complete の popup の状態によって再度表示し直す"
(when ayc-minor-mode
(ayc-log "reposition: e%S / %S" ac-menu ayc-preview-popup)
(cond
((and (ac-menu-live-p) ; ac-menu が表示されていて
(not (popup-hidden-p ac-menu))
ayc-preview-popup (popup-p ayc-preview-popup)) ; プレビューがでているとき
;; 消す
(popup-delete ayc-preview-popup)
(setq ayc-preview-popup nil))
((and (not (and (ac-menu-live-p) ; ac-menu が表示されていなくて
(not (popup-hidden-p ac-menu))))
(not (popup-live-p ayc-preview-popup))) ; ひらがなプレビューが出てないとき
(ayc-preview-show)))))
(defadvice ac-update-candidates (after iiimcf-ayc-ac)
(ayc-preview-reposition))
(ad-activate-regexp "^iiimcf-ayc-ac$")
;;(ad-deactivate-regexp "^iiimcf-ayc-ac$")
;;(ad-unadvise 'ac-update-candidates)
;;==================================================
;; Commit Hacks
(defvar ayc-last-command-event nil)
(make-variable-buffer-local 'ayc-last-command-event)
;; (defadvice call-interactively (around iiimcf-ayc-preedit)
;; (ayc-log "call-interactively:(%s) %S [%S]" (current-buffer)
;; this-command (this-command-keys))
;; ad-do-it)
(defadvice iiimcf-server-control-keyforward (around iiimcf-ayc-preedit)
"[hack] iiimcf は一旦 preedit モードになるとキー入力を自前で全部処理してしまうので
この関数を監視してキー入力を保存しておく。"
(when ayc-minor-mode
(let ((ch (ignore-errors keyseq)))
(setq ayc-last-command-event
(if (and (stringp ch)
(< 31 (aref ch 0))
(< (aref ch 0) 128))
ch nil)))
(ayc-log "server-control-keyforward:(%s) %S / %S [%S]"
(current-buffer)
this-command last-command
ayc-last-command-event))
ad-do-it)
;;(ad-activate-regexp "^iiimcf-ayc-preedit$")
;;(ad-deactivate-regexp "^iiimcf-ayc-preedit$")
;;(ad-unadvise 'iiimcf-server-control-keyforward)
(defun ayc-commit-hook ()
"[hack] 変換確定された後で呼ばれる。ここで一旦 IM 状態を切って、連続して入力できるようにする。
続けて文字を入力する場合は、先頭の文字が iiimcf に食べられてしまうので、
ここで iiimcf-server-control-keyforward アドバイスを使って救う。"
;; str: from `iiimcf-UI-commit-string'
(ayc-log "commit-hook:(%s) %s" (current-buffer) str)
(ayc-record-commit-string ayc-last-invoked-string str)
(setq ayc-last-invoked-string nil)
(ignore-errors
(setq unread-command-events
(append unread-command-events
(if ayc-last-command-event
(list 'backspace))
(listify-key-sequence
(where-is-internal 'toggle-input-method global-map t))
(if ayc-last-command-event
(listify-key-sequence ayc-last-command-event))
))
(ayc-log "commit-hook: unread %S" unread-command-events)
(ayc-log "commit-hook: ayc-last-key [%S]" ayc-last-command-event)))
(defun ayc-inactive-hook ()
"[hack] ayc-commit-hook でIM 状態が非アクティブになるので、その
後このフックが呼ばれてプレビュー状態が再開される。"
(ayc-preview-start)
(setq ayc-last-invoked-string nil)
(ayc-log "inactive-hook:(%s) unread %S" (current-buffer) unread-command-events)
(setq ayc-last-command-event nil))
;;==================================================
;; 補完関係
;; ○ayc-record 構造体
;; original : 元のローマ字
;; commmitted : 変換された単語
;; used-num : 累積採用回数
;; last-used-time : 前回使った時間 (float-time)
(defstruct ayc-record original committed used-num last-used-time)
(defvar ayc-record-history-file (expand-file-name ".ayc-history" user-emacs-directory)
"補完文字列を保存する場所")
(defvar ayc-record-last-saved-time nil "最後に保存した時間。これよりもファイルの方が新しければ読み直す。")
(defvar ayc-record-list-num 500 "履歴保存数")
(defvar ayc-record-list nil "変換履歴")
(defun ayc-record-save ()
"変換履歴を書き込む。タイムスタンプを比較して、ファイルの方が新しければ書き込まない。"
(let ((file (expand-file-name ayc-record-history-file)))
(when (or (not (file-exists-p file))
(null ayc-record-last-saved-time)
(time-less-p (nth 5 (file-attributes file))
ayc-record-last-saved-time))
(let* ((coding-system-for-write 'utf-8)
after-save-hook before-save-hook
(buf (find-file-noselect file)))
(unwind-protect
(with-current-buffer buf
(buffer-disable-undo)
(erase-buffer)
(insert
(prin1-to-string
(loop for i in (ayc-record-sort ayc-record-list)
for idx from 0 below ayc-record-list-num collect
(list
(ayc-record-original i)
(ayc-record-committed i)
(ayc-record-used-num i)
(ayc-record-last-used-time i)))))
(save-buffer))
(kill-buffer buf)))
(setq ayc-record-last-saved-time (current-time))
(ayc-log "Saved history list (%i)" (length ayc-record-list))))
nil)
(defun ayc-record-load ()
"変換履歴を読み込む。タイムスタンプを比較して手元のデータの方が新しければ読み込まない。"
(let* ((coding-system-for-read 'utf-8)
(file (expand-file-name ayc-record-history-file)))
(when (and (file-exists-p file)
(or (null ayc-record-last-saved-time)
(time-less-p ayc-record-last-saved-time
(nth 5 (file-attributes file)))))
(let ((buf (find-file-noselect file)) ret)
(unwind-protect
(setq ret (loop for i in (read buf)
collect
(make-ayc-record
:original (car i)
:committed (nth 1 i)
:used-num (nth 2 i)
:last-used-time (nth 3 i))))
(kill-buffer buf))
(setq ayc-record-last-saved-time (nth 5 (file-attributes file)))
(setq ayc-record-list ret)
(ayc-log "Load history list (%i)" (length ret))))))
(defun ayc-record-search-exact (key)
"完全一致で検索する。見つからなかったら nil を返す。"
(loop for i in ayc-record-list
if (equal key (ayc-record-original i))
return i))
(defun ayc-record-commit-string (original committed)
"確定文字列を記録する"
(let ((prev (ayc-record-search-exact original)))
(cond
((and prev (equal committed (ayc-record-committed prev)))
(incf (ayc-record-used-num prev))
(setf (ayc-record-last-used-time prev) (float-time))
(ayc-log "record hit: %s / %i" original (ayc-record-used-num prev)))
(t (push (make-ayc-record
:original original :committed committed
:used-num 1 :last-used-time (float-time))
ayc-record-list) ; 既にロードされていることが前提
(ayc-log "add record: %s" original)))
(setq ayc-record-last-saved-time (current-time))))
(defun ayc-record-search-prefix (key)
"先頭一致で検索する。結果はソートして返す。"
(loop with ret = nil
with key-len = (length key)
for i in ayc-record-list
for org = (ayc-record-original i)
if (loop with len = (min key-len (length org)) ; 正規表現とどっちが速いか...
for p from 0 below len
unless (eql (aref key p) (aref org p))
return nil finally return t)
do (push i ret)
finally return (ayc-record-sort ret)))
(defun ayc-record-sort (lst)
"リストをソートして返す"
(sort lst
(lambda (x y) ; <
(let ((xu (ayc-record-used-num x))
(yu (ayc-record-used-num y)))
(cond
((> xu yu) t) ; 使用回数の多い方
((= xu yu)
(> (ayc-record-last-used-time x) ; 最近の方
(ayc-record-last-used-time y)))
(t nil))))))
(defvar ayc-record-candidate-org-max 16 "一覧に出す補完ローマ字の最大文字数")
(defvar ayc-record-candidate-com-max 20 "一覧に出す補完変換文字列の最大幅")
(defvar ayc-record-candidate-show-document nil "補完一欄内で完全な内容を表示するかどうか")
(defun ayc-record-candidate-transient-item (str)
"ひらがな変換用のレコード作成"
(make-ayc-record
:original str :committed (ayc-romaji-to-kana str)
:used-num 1000000 :last-used-time (float-time)))
(defun ayc-record-candidates ()
"auto-complete 用の関数"
(condition-case err
(loop with search-list = (ayc-record-search-prefix ac-prefix)
with clist = (if search-list
(append
(list (ayc-record-candidate-transient-item
(substring-no-properties ac-prefix)))
search-list))
for i in clist
for org = (ayc-record-original i)
for com = (ayc-record-committed i)
collect
(cons
(propertize
(substring-no-properties
org 0 (min (length org) ayc-record-candidate-org-max))
'summary (truncate-string-to-width
(substring com 0) ayc-record-candidate-com-max 0 nil t)
(if ayc-record-candidate-show-document 'document 'x-document)
(format "%s\n%s" org com))
i))
(t (ayc-log "ERROR : %S" err))))
(defun ayc-record-complete-action ()
"auto-complete で補完されたときに呼ばれる。"
(ayc-log "COMPLETE: %S" candidate)
(when (and candidate (get-text-property 0 'value candidate))
(let ((record (get-text-property 0 'value candidate)))
;; auto-complete で既に入力されているものを消す
(when (re-search-backward (regexp-quote candidate))
(replace-match ""))
(insert (ayc-record-committed record))
(incf (ayc-record-used-num record))
(setf (ayc-record-last-used-time record) (float-time)))))
(defun ayc-record-complete-prefix ()
(let ((poss (ayc-collect-word)) (pose (point)))
;;(ayc-log "ac-prefix: %s" (buffer-substring-no-properties poss pose))
(and (< poss pose) poss)))
(defun ayc-record-setup-auto-complete ()
"auto-complete 用の諸々をセットアップする。"
(unless (ignore-errors (symbol-value 'ac-source-iiimcf))
(ac-define-source iiimcf
'((candidates . ayc-record-candidates)
(action . ayc-record-complete-action)
(symbol . "d")
(prefix . ayc-collect-word)))
(unless (assoc-default 'ayc-collect-word ac-prefix-definitions)
(ac-define-prefix 'ayc-collect-word
'ayc-record-complete-prefix))))
(defvar ayc-record-auto-complete nil "AYC で auto-complete 使うかどうか.")
(defvar ayc-record-auto-complete-backup nil "auto-complete-mode のバックアップ。")
(make-variable-buffer-local 'ayc-record-auto-complete-backup)
(defvar ayc-record-disable-faces nil "auto-complete の補完をさせたくない face 名のリスト。")
(defvar ayc-record-disable-faces-backup nil "ac-disable-faces のバックアップ。")
(make-variable-buffer-local 'ayc-record-disable-faces-backup)
(defvar ayc-record-ac-quick-help-delay-backup nil "ac-quick-help-delay のバックアップ。")
(defvar ayc-record-ac-quick-help-delay 1.5 "ac-quick-help ( 完全な変換文字列表示用のポップアップ ) 表示までの時間。なるべく速い方が便利そうなので。")
(defun ayc-record-ac-on ()
"minor-mode on 時の auto-complete 開始処理。"
(when ayc-record-auto-complete
(ayc-record-load)
(ayc-record-setup-auto-complete)
(setq ayc-record-disable-faces-backup ac-disable-faces ; ac-disable-faces 待避
ac-disable-faces ayc-record-disable-faces ; ac-disable-faces 入れ替え
ayc-record-ac-quick-help-delay-backup ac-quick-help-delay ; ac-quick-help-delay 待避
ac-quick-help-delay ayc-record-ac-quick-help-delay ; ac-quick-help-delay 入れ替え
ayc-record-auto-complete-backup auto-complete-mode) ; auto-complete-mode 待避
(unless (memq 'ac-source-iiimcf ac-sources) ; ac-sources 入れ替え
(push 'ac-source-iiimcf ac-sources))
(auto-complete-mode 1)))
(defun ayc-record-ac-off ()
"minor-mode off 時の auto-complete 終了処理。"
(when ayc-record-auto-complete
(when (memq 'ac-source-iiimcf ac-sources) ; ac-sources 戻す
(setq ac-sources (delete 'ac-source-iiimcf ac-sources)))
(setq ac-disable-faces ayc-record-disable-faces-backup) ; ac-disable-faces 戻す
(ayc-record-save)
(unless ayc-record-auto-complete-backup ; auto-complete-mode 戻す
(auto-complete-mode -1))
(setq ayc-record-auto-complete-backup nil)))
;;==================================================
;; 辞書メンテナンス
(defun ayc-record-remove (records)
"辞書メンテナンス。辞書から削除する。"
(dolist (i records)
(ayc-log "reocrd remove: %S" i)
(setq ayc-record-list (delete i ayc-record-list))))
(defun ayc-record-reset-used-count (record)
"辞書メンテナンス。利用回数カウンタを 1 にもどす。"
(dolist (i records)
(ayc-log "reocrd reset: %S" i)
(setf (ayc-record-used-num i) 1)))
(defun ayc-record-anything-source ()
"辞書メンテナンス用の anything source を返す"
'((name . "Accounts : ")
(candidates
. (lambda ()
(mapcar
(lambda (record)
(cons (concat
"[" (number-to-string (ayc-record-used-num record)) "] "
(ayc-record-committed record)
" / " (ayc-record-original record)
" " (format-time-string
"%Y/%m/%d %H:%M:%S"
(seconds-to-time (ayc-record-last-used-time record))))
record))
ayc-record-list)))
(action
. (("Remove"
. (lambda (ignore)
(when (y-or-n-p "Rmove the records? ")
(ayc-record-remove (anything-marked-candidates)))))
("Reset used-count to 1"
. (lambda (record)
(ayc-record-reset-used-count (anything-marked-candidates))))))))
(defun ayc-record-anything ()
"辞書メンテナンス anything"
(interactive)
(anything (ayc-record-anything-source)))
;;==================================================
;; 逆変換
(defvar ayc-last-commit-point nil "最後に変換確定した場所。ここ以外での確定キャンセルは、形態素解析に回す。")
(make-variable-buffer-local 'ayc-last-commit-point)
(defvar ayc-reverse-delimiter-regexp
(concat "[][\n\r\t "
",.?!()-=+|/&$#@~:;\"{}<>"
"、。 ※・?!()=+|&$#@:;”’"
"{}<>「」【】○●◎■□◇◆△▲▽▼]")
"逆変換の先頭を探す目印の正規表現")
(defvar ayc-reverse-through-delimiters
"、。※・?!()=+|&$#@:;”’{}<>「」【】○●◎■□◇◆△▲▽▼"
"直前ならやっぱり逆変換に含めたい文字列")
(defun ayc-reverse-search-begin ()
"現在のポイント位置から前に走査して、逆変換を開始する位置を返す。
見つからなかったら現在のポイント位置を返す。"
(save-excursion
(when (and ; 直前の文字が through-delimiters に含まれていれば無視する
(char-before)
(loop with prechar = (char-before)
for i across ayc-reverse-through-delimiters
if (eql i prechar)
return t finally return nil))
(goto-char (1- (point))))
(or
(and (re-search-backward ayc-reverse-delimiter-regexp nil t)
(1+ (point)))
(line-beginning-position))))
(defvar ayc-reverse-translate-driver 'ayc-reverse-translate-driver-mecab "形態素解析に何を使うか。
今のところ 'ayc-reverse-translate-driver-mecab / 'ayc-reverse-translate-driver-chasen が使える。
ドライバーは変換済み文字列を受け取って、元の単語と読みのひらがなの alist を返す。")
(defun ayc-reverse-translate-driver-mecab (kanji)
"MeCab driver"
(let (result)
(with-temp-buffer
(call-process-shell-command
(format "echo '%s' | mecab " kanji) nil t)
(goto-char (point-min))
(while (re-search-forward "^\\([^\t]+\\)\t\\(.*\\)$" nil t)
(let* ((org (match-string 1))
(cols (match-string 2))
(yomi (nth 7 (split-string cols ","))))
(push (cons org
(japanese-hiragana
(if yomi yomi org)))
result))))
(nreverse result)))
(defun ayc-reverse-translate-driver-chasen (kanji)
"ChaSen driver"
(let (result)
(with-temp-buffer
(call-process-shell-command
(format "echo '%s' | chasen " kanji) nil t)
(goto-char (point-min))
(while (re-search-forward "^\\([^\t]+\\)\t\\([^\t]*\\)\t.*$" nil t)
(let* ((org (match-string 1))
(yomi (match-string 2)))
(push (cons org
(japanese-hiragana
(if (or (string-match "[0-9a-zA-Z]" org)
(or (null yomi) (= 0 (length yomi))))
org yomi)))
result))))
(nreverse result)))
(defun ayc-reverse-translate-driver (kanji)
"漢字からひらがな・単語区切りにする。実際にはドライバーに丸投げする。"
(funcall ayc-reverse-translate-driver kanji))
(defun ayc-reverse-kanji-to-kana (kanji)
"漢字からひらがなにする。"
(let ((words (ayc-reverse-translate-driver kanji)))
(when words
(loop for (org . kana) in words
concat kana))))
(defvar ayc-kana-regexp
(regexp-opt
(mapcar 'cdr (sort (copy-sequence ayc-romaji-kana-table)
(lambda (i j) (> (length (cdr i))
(length (cdr j)))))))
"かなを拾う正規表現。 ayc-romaji-kana-table を更新したらこの変数も更新すること。")
(defun ayc-reverse-kana-to-romaji (source)
"アルファベットローマ字にする。"
(with-temp-buffer
(insert source)
(goto-char (point-min))
(while (re-search-forward ayc-kana-regexp nil t)
(let ((match (match-string 0)))
(replace-match
(car (rassoc match ayc-romaji-kana-table)))))
(buffer-string)))
(defun ayc-reverse-test ()
"逆変換のテスト"
(interactive)
(pop-to-buffer "*atok-kanji-to-romaji-test*")
(erase-buffer)
(loop for q in '(ayc-reverse-translate-driver-mecab
ayc-reverse-translate-driver-chasen)
do
(let ((ayc-reverse-translate-driver q))
(insert "==(" (symbol-name q) ")==========\n")
(loop for (in out) in
'(("太郎は花子が好きだ" "tarouhahanakogasukida") ; 普通
("今日は良い天気ですね" "kyouhayoiten'kidesune") ; ん
("東京特許許可局" "toukyoutoxtukyokyokakyoku") ; 小さいつ
("1太郎は花子が好きだ" "1tarouhahanakogasukida") ; 数字込み
("デフォルトのカーソールの色" "deforutonoka-so-runoiro") ; 未知語
("AはBを含む。" "AhaBwohukumu.") ; アルファベット込み
("MySQLにJSONを登録する。" "MySQLniJSONwotourokusuru.")) ; 英単語込み
for kana = (ayc-reverse-kanji-to-kana in)
for romaji = (ayc-reverse-kana-to-romaji kana)
do
(insert (format "%s : %s / %s / %s <= %s\n"
(if (equal romaji out) "OK" "NG")
out romaji kana in))))))
;; (ayc-reverse-test)
(defun ayc-reverse-translate ()
"現在のポイント位置から逆変換を試みる"
(interactive)
(let ((poss (if (region-active-p)
(min (mark) (point))
(ayc-reverse-search-begin)))
(pose (if (region-active-p)
(max (mark) (point)) (point)))
org romaji)
(when (< poss pose)
(setq org (buffer-substring poss pose)
romaji (ayc-reverse-kana-to-romaji
(ayc-reverse-kanji-to-kana org)))
(delete-region poss pose)
(ayc-preview-stop)
(setq ayc-last-invoked-string romaji)
(call-interactively 'toggle-input-method)
(setq unread-command-events
(append unread-command-events
(listify-key-sequence romaji) '(?\ ))) ;; ※サーバー設定依存
(ayc-log "reverse: unread %S" unread-command-events))))
;;==================================================
;; API / Commands
(defun ayc-hiragana ()
"ローマ字を拾ってひらがなにして変換確定する"
(interactive)
(ayc-invoke '(return))) ;; ※サーバー設定依存
(defun ayc-henkan ()
"ローマ字を拾って変換する"
(interactive)
(ayc-invoke '(?\ ))) ;; ※サーバー設定依存
(defun ayc-katakana ()
"ローマ字を拾ってカタカナにして変換確定する"
(interactive)
(ayc-invoke '(f7 return))) ;; ※サーバー設定依存
(defun ayc-insert-space ()
(interactive)
(let ((poss (ayc-collect-word)))
(when (< poss (point))
(ayc-auto-adjust-space-insert poss)))
(insert " "))
(defun ayc-backward-delete-char-length ()
"ayc-romaji-kana-table から総当たりで最長一致の場所を見つける。
(re-search-backward は最短一致でしか見つけてくれないため)"
(loop with start = (point)
with maxlen = 0
for (romaji . kana) in ayc-romaji-kana-table
for len =
(loop with len = (length romaji)
for i from 0 below len
unless (eql (char-before (- start i)) (aref romaji (- len i 1)))
return nil finally return len)
if len do (setq maxlen (max maxlen len)) (ayc-log "HIT : %s" len)
finally return maxlen))
(defun ayc-backward-delete-char ()
"ローマ字単位で一文字バックスペースする"
(interactive)
(cond
(ayc-minor-mode
(backward-delete-char (max 1 (ayc-backward-delete-char-length))))
(t (backward-delete-char 1))))
(defun ayc-delete-char ()
"ローマ字単位で一文字削除削除する"
(interactive)
(unless
(and t ;ayc-minor-mode
(save-excursion
(let ((start (point))
(end (re-search-forward ayc-romaji-regexp nil t)))
(when (and end (= (- end start) (length (match-string 0))))
(delete-region start end) t))))
(delete-char 1)))
(defun ayc-complete ()
"auto-complete で補完をする。 ayc-record-auto-complete が nil の時に使うと便利。"
(interactive)
(ayc-record-setup-auto-complete)
(let ((auto-complete-mode t))
(auto-complete (list ac-source-iiimcf))))
(defun ayc-preview-start ()
"プレビュー表示を開始する。漢字変換中は停止する。"
(ayc-log "preview-start")
(add-hook 'post-command-hook 'ayc-post-command-hook nil t))
(defun ayc-preview-stop ()
"プレビュー表示を停止する。"
(ayc-log "preview-stop")
(remove-hook 'post-command-hook 'ayc-post-command-hook t)
(when ayc-preview-popup
(and ayc-preview-popup (popup-p ayc-preview-popup))
(popup-delete ayc-preview-popup)
(setq ayc-preview-popup nil)))
;;==================================================
;; Minor Mode Definition
(defun ayc-minor-mode-off-command ()
(interactive)
(ayc-minor-mode -1))
(defun ayc-define-keymap (keymap-list)
(let ((map (make-sparse-keymap)))
(mapc
(lambda (i)
(define-key map
(if (stringp (car i))
(read-kbd-macro (car i)) (car i))
(cdr i)))
keymap-list)
map))
(defvar ayc-minor-mode-keymap
(ayc-define-keymap
'(
("RET" . ayc-hiragana)
("SPC" . ayc-henkan)
("S-SPC" . ayc-insert-space)
("<backspace>" . ayc-backward-delete-char)
("C-h" . ayc-backward-delete-char)
("<delete>" . ayc-delete-char)
("C-d" . ayc-delete-char)
("C-<backspace>" . ayc-reverse-translate)
("TAB" . ayc-complete)
)))
(defvar ayc-cursor-color-backup nil "前のカーソールの色を覚えておく")
(make-variable-buffer-local 'ayc-cursor-color-backup)
(defvar ayc-cursor-color-backup-default (ayc-get-current-cursor-color)
"デフォルトのカーソールの色。 ayc-cursor-color-backup が存在しな
い場合で元の色に戻したいときにこの色にする。")
(defvar ayc-minor-mode-hook nil)
(defvar ayc-minor-mode nil) ; dummy
(defun ayc-minor-mode-move ()
(let ((pair (assq 'ayc-minor-mode minor-mode-map-alist)))
(when pair
(setq minor-mode-map-alist
(cons pair (delq pair minor-mode-map-alist))))))
(define-minor-mode ayc-minor-mode
"Yet Another YC mode"
:init-value nil
:lighter " 漢"
:keymap ayc-minor-mode-keymap
:group 'ayc-mode
(if ayc-minor-mode
(progn
(ayc-minor-mode-setup)
(run-hooks 'ayc-minor-mode-hook))
(ayc-minor-mode-abort)))
(defun ayc-minor-mode-setup ()
(add-hook 'pre-command-hook 'ayc-pre-command-hook) ; global
(ad-activate-regexp "^iiimcf-ayc-preedit$")
(add-hook 'input-method-after-insert-chunk-hook 'ayc-commit-hook nil t)
(add-hook 'input-method-inactivate-hook 'ayc-inactive-hook nil t)
(add-hook 'isearch-mode-hook 'ayc-preview-stop nil t)
(add-hook 'isearch-mode-end-hook 'ayc-preview-start nil t)
(ayc-minor-mode-move)
(setq ayc-last-command-event nil)
(unless ayc-cursor-color-backup
(setq ayc-cursor-color-backup
(frame-parameter (selected-frame) 'cursor-color)))
(set-cursor-color ayc-cursor-color)
(ayc-record-ac-on)
(ayc-preview-start))
(defun ayc-exist-active-buffer-p ()
"ayc-minor-mode が on になっているバッファがあれば t を返す。
アドバイスやフックを消すタイミングを決定するために調べる。"
(loop for i in (buffer-list)
if (buffer-local-value 'ayc-minor-mode i)
return t))
(defun ayc-minor-mode-abort ()
(ayc-preview-stop)
(setq ayc-last-command-event nil)
(remove-hook 'input-method-after-insert-chunk-hook 'ayc-commit-hook t)
(remove-hook 'input-method-inactivate-hook 'ayc-inactive-hook t)
(remove-hook 'isearch-mode-hook 'ayc-preview-stop t)
(remove-hook 'isearch-mode-end-hook 'ayc-preview-start t)
(unless (ayc-exist-active-buffer-p)
(ad-deactivate-regexp "^iiimcf-ayc-preedit$")
(remove-hook 'pre-command-hook 'ayc-pre-command-hook))
(if current-input-method ;; IM も切る
(inactivate-input-method))
(ayc-record-ac-off)
(when ayc-cursor-color-backup
(set-cursor-color ayc-cursor-color-backup)
(setq ayc-cursor-color-backup nil)))
(defun toggle-ayc-minor-mode ()
(interactive)
(ayc-minor-mode
(if ayc-minor-mode -1 1)))
;; (eval-current-buffer)
;; (global-set-key (kbd "C-o") 'toggle-ayc-minor-mode)
;; (set-cursor-color "black")
;; iiimcf-server-control-enable
;; (inactivate-input-method)
;; (setq iiimcf-server-control-enable nil)
;; (iiimcf-server-control-delete-ic iiimcf-server-control-ic-id)
(provide 'iiimcf-yc)
;;; iiimcf-yc.el ends here
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment