Image by Stable Diffusion
ノート/メモ管理アプリ「Obsidian」を使い始めて9ヶ月ほど経過した。
デイリーノートの一行日記をはじめ、趣味のテキスト書きや日曜プログラミング、生成AIの成果まとめ、資料の整理等で、すっかりObsidian抜きのパソコン作業は考えられなくなった。
パソコンを使い始めて●年経つのだが、今頃になってこうすればもっと便利になる、使いやすくなるというものを色々と試している。
そして今回は「Obsidian上で、いま日本語入力状態かどうかを知りたい」ということに取り組んだ。
習慣づいてないIME切り替え
Obsidianはただのプレーンなテキストとして記述もできる。
でも、マークダウン方式に則って記述すれば見出し等で見た目もよくなるし、アウトラインやリンク等の機能を使うこともできる。
結果として、日本語入力方法(IME)がオンで、全角のままマークダウンを打ち始めて上手く半角変換できなかったり、気づいてバックスペースで戻ってIMEオフして半角英数字記号で打ち直すシーンがたびたびあり、ちょっとストレスになっていた。
私はプログラムらしきものを書いてたこともあるが、本職のシステム屋さんほどキーボードオンリーで全て為すとか徹底できておらず、IME切り替えの管理もずさんだ。
今回も調査途中に聞いた話でIMEオフを習慣づけてる方もいらしたが、今からこの習慣を身につけるのは大変。
アプリを検討する
そこでIMEオン・オフ状態をキャレット(caret。挿入位置の縦線)の色を変更するなどで示すアプリを探した。
IME indicator
Windows 10のアップデートで途中から追加された新機能「テキストカーソルインジケーター」を応用して、IME状態を色の変化で教えてくれるアプリ。実はAutoHotkey製アプリの模様。
OS標準機能を用いているからObsidianに限らず表示され、これで問題は解決できるのだが、如何せん見た目があまり好きではない。
今回、解決できなかったら我慢してこれを使おうと思っていた。
ImeCaretColor
IME状態に応じてキャレットの色と幅を変化させるアプリ。
シンプルイズベスト。まさに求めていたものだが、ブラウザ上では有効にならず、Chromeエンジンが使われているObsidianでも有効にならない。
仕組みを調査する
ImeCaretColorが使えなかったのは残念だが、逆を言えばChromeエンジン、HTMLやCSS、JavaScriptで解決できるかもしれない。
そちらにアプローチを変えて再調査。
CSS
Options to modify cursor style – Share & showcase – Obsidian Forumで、CSSを使ってキャレットの色を変えられることを知った。
しかし、CSS Basic User Interface Module Level 4 (日本語訳)等をあたっても、CSSではIMEの状態を区別するプロパティがない。
JavaScript
[JavaScript]日本語IME入力中か判定する方法 – Qiitaによれば、JavaScriptでおおよそIMEの状態を知る手がかりはありそう。
しかし、Obsidian上でJavaScriptを実行させるのはTemplaterプラグイン等で都度実行する必要があり、常時JavaScriptに監視させて状態変化を知らせるなどは、Obsidianプラグインの領域だろう。
Obsidianプラグインは開発環境を整える必要があり、今の私にとって敷居が高い。JavaScriptも慣れてないので壁も高い。
CSS変更が有効な時点で、私の中では「プランB」が生まれていた。
AutoHotkeyで推参(おしてまいる)
AutoHotkeyでCSSを書き換える
「常駐AutoHotkeyスクリプトでIME状態を監視し、状態変更があったらCSSスニペットを書き換える」が、私のプランB。
AutoHotkeyはWindowsのシステム環境情報を拾うのが得意で、界隈でも著名な「IME.ahk」を応用すればIME状態を取得可能。
Obsidianの取り扱うファイルはObsidian外から直接編集することができ、すでにAutoHotkeyで.md(マークダウン)ファイルを操作するスクリプトは作った経験がある。
CSSを凝ってみる
CSSでキャレットの色変更は確認できた。
そこに hann-soloさんから「行の色を変えても、かわいらしくておもしろいかも」という素敵なアイデアを頂いた。
そういえばそのような画面を以前見たことあったなと記憶をたどり、winerosesさんのブログ記事を再発見。
Obsidianの Minimal Theme Settings テーマプラグインと Style Settings プラグインは導入済みだったので、カーソル行の背景色変更はすでに用意されている。
あとはこれらを指定しているCSSを特定するだけ。
Ctrl+Shift+I キーでChromeデベロッパーツールを開き、HTMLソースを一つずつほぐしていきます。
2画面表示で左右にノートを開いていて、選択したノートのある方には「.mod-active」が入る。これがアクティブ画面のCSSと推定。
先のキャレット色変更の際に、編集時のノートを示すのが「.cm-editor」と判明済み。
さらにキャレットを置いている行には「.cm-active」が追加されるので、これが選択行のCSSと確定。
<Vault>\.obsidian\themes\Minimal\theme.css
の中から「cm-active」を含む部分を抽出。
「background-color:var(--active-line-bg)
」とあるので、ここが行の背景色。--active-line-bg
はCSS変数?
そんなの今さら知ったのだけど、指定を2箇所発見。たぶんダークテーマとライトテーマ用? RGBカラーコードと透明度の指定ですか。
以上で材料はそろったので、さっそく調理に取りかかりましょう。
IME入力状態が分かるようになった!
完成例
まずは出来上がりからご覧ください。
IMEオフ時は、選択行の背景が暗く、キャレットは白色。
IMEオン時、日本語入力時には、選択行の背景が少し明るく、キャレットは緑色。
若干の遅れはあるものの、IME切り替えの変化に追随しています。
これで悩みの種だった「見た目では、いまの入力状態がIMEオンかオフか分からない」が解決できました。
設定内容
今回の再現をお手元で再現するには、以下が必要となります。
- Obsidian Minimal Theme Settingsプラグイン(背景行変更)
- Obsidian Style Settingsプラグイン(背景行変更)
- Style Settingsを開き、
Minimal - Line numbers - Highlight active line
を有効にする
- Style Settingsを開き、
- Obsidian オプション設定
- 以下で追加する3つのCSSを
<Vault>\.obsidian\snippets
に置く。 - オプションの外観を開き、以下で追加するCSSの内、
cursor.css
だけを有効にする。他2つは無効のまま。
- 以下で追加する3つのCSSを
- AutoHotkey v1
スクリプトサンプル
- 以下のCSSはダークテーマの設定例です。色指定は適宜変更を。
- 色指定の変更は、cursor-IMEoff.css または IMEon.css を。
- AutoHotkeyスクリプトを起動し常駐しておくことが条件です。
- スクリプトが0.5秒毎にチェックしています。
- Obsidian上でIME切り替えを行うと、cursor-IMEoff.css または IMEon.css を、cursor.css に上書きコピーします。
- IME.ahkは、eamatさんの配布をお借りしました。
cursor.css
/* IME caret color Change by AutoHotkey */
.cm-editor * {
caret-color: auto !important;
}
cursor-IMEoff.css
/* IME caret color Change by AutoHotkey Desktop
default
caret-color: auto !important;
--active-line-bg:rgba(255,255,255,0.04);
*/
.mod-active .cm-editor * {
caret-color: white !important;
--active-line-bg:rgba(0,0,0,0.16);
}
cursor-IMEon.css
/* IME caret color Change by AutoHotkey Desktop */
.mod-active .cm-editor * {
caret-color: green !important;
--active-line-bg:rgba(255,255,255,0.04);
}
sample.ahk
/* ------------------------------------------------------------
; 常時起動しておくAutoHotkeyスクリプト例
*/
#Persistent
#SingleInstance, Force
#NoEnv
Menu, Tray, Tip, desktop_AutoHotkey
pathIme := "C:\<Your Vault Address>\.obsidian\snippets\cursor.css"
pathIme1 := "C:\<Your Vault Address>\.obsidian\snippets\cursor-IMEon.css"
pathIme0 := "C:\<Your Vault Address>\.obsidian\snippets\cursor-IMEoff.css"
iSleep := 500 ; 0.5秒毎に更新
GoSub, TimerAction
Sleep, 50
SetTimer, TimerAction, Off
SetTimer, TimerAction, %iSleep%
Return
/* ------------------------------------------------------------
; TimerAction:タイマー処理
*/
TimerAction:
; IMEオンオフの監視
IfWinActive, ahk_exe Obsidian.exe
{
WinGetActiveTitle, vTmp
iIme := IME_GET(vTmp) ; 対象アクティブ窓のIME状態取得
If ( iImePre <> iIme )
{
If ( iIme == 1 )
FileCopy, %pathIme1%, %pathIme%, 1
Else
FileCopy, %pathIme0%, %pathIme%, 1
}
iImePre := iIme
}
Return
/* ------------------------------------------------------------
; IMEの状態の取得 from IME.ahk
WinTitle="A" 対象Window
戻り値 1:ON / 0:OFF
*/
IME_GET(WinTitle="A") {
ControlGet,hwnd,HWND,,,%WinTitle%
If (WinActive(WinTitle)) {
ptrSize := !A_PtrSize ? 4 : A_PtrSize
VarSetCapacity(stGTI, cbSize:=4+4+(PtrSize*6)+16, 0)
NumPut(cbSize, stGTI, 0, "UInt") ; DWORD cbSize;
hwnd := DllCall("GetGUIThreadInfo", Uint,0, Uint,&stGTI)
? NumGet(stGTI,8+PtrSize,"UInt") : hwnd
}
Return DllCall("SendMessage"
, UInt, DllCall("imm32\ImmGetDefaultIMEWnd", Uint,hwnd)
, UInt, 0x0283 ;Message : WM_IME_CONTROL
, Int, 0x0005 ;wParam : IMC_GETOPENSTATUS
, Int, 0) ;lParam : 0
}