smalltalk.connpass.com
私にとって Smalltalk は学生時代から憧れの言語で、にもかかわらず何度挑戦しても (Emacs keybind が使えないがゆえに) 挫折を繰り返していた言語です。何とか突破口を開けられないかと参加させてもらいました。
以下、教えて頂いたこと・調べたことの備忘録です。間違いなど有るかもしれませんのでツッコミ歓迎です。
私は Smalltalk 初心者なので、Pharo5 のキーボード・マッピング周りの仕組みを調べて学ぶことにしました。
途中、梅澤さん mumez (Masashi Umezawa) · GitHub に Pharo のショートカット周りのクラスやそのオーバーライド方法、
クラスブラウザの操作方法を色々教えて頂きました。
PharoShortcuts current
で取り出されるインスタンスを通して、ショートカットが設定されているらしいので、ここをオーバーライドすれば出来そう、とのこと。(ただ、そのために PharoShortcuts クラス自体もいじらなければならない。)
- この使用箇所を探すとき、クラスブラウザ上でクラスを選んで右クリック→
Analyze
→ Class refs...
が役に立つことを見せて頂きました。
- インスタンス変数は大文字はじまり、と。
- (Web ブラウザの開発ツールに有るような) マウスで画面要素を直接指して中身を Inspect したい時は、
Shift + Mouse Middle
で Halo を呼びだせば良いそう。
雑談
お昼ごはんの時にも雑談で色々教えて頂きました。
- イベントループはどこに有るの? →
WorldState
にあるよ
- Pharo にも Object Table は有るの? → ある。 Newspeak, Pharo, Squeak は VM (の設計?) が同じ
allInstance
の逆はあるの? → allOwners
というのがある。(Morph のみ?)
- バージョン管理機能があると聞いたけど、 CompiledMethod はイメージ内に複数持つの?一つだけ? → 一つだけ。
あと、皆さん pharo を一番使ってたのが印象的でした。 pharo のコア開発者はフランス inria 繋がりの人が多いとか…
<keymap>
は Pragma だった!
以前の挫折ポイントの一つが、この↓ <keymap>
TxTextEditorMorph>>#buildTextEditorKeymapsOn: aBuilder
<keymap>
{
Character home. #moveToLineStart.
Character home shift. #selectToLineStart.
...
これは Pharo の Pragma 構文で、所謂 コード注記 と同じ役割を果たしているそうです。
(メソッドに属性としてぶら下がる)。
Pharo は Pragma を色々活用しているとのこと。
ところが PharoShortcuts
の中身をよく見たところ、肝心のカーソル移動が定義されていないことに気づきました。
これでは Emacs 風のカーソル移動は定義できません。
そこで方針を変えて、そもそもキーマップ機能と、イベントループ周りがどんな仕組みになっているのかを調べることにしました。
キーマップ関連
TxTextEditorMorph>>#initializeShortcuts: aKMDispatcher
aKMDispatcher attachCategory: #TxTextEditorMorph
KMDispatcher というのが関係が深そう…
クラスのコメントを順々に抜き書きしてみます。
KMDispatch
I'm an object that saves a buffer of keyevents for the morph I'm attached.
I am the one that dispatches the single and multiple shortcuts.
If the morph has a keymap that matches the keyboard event,
I tell the keymap event to execute with the morph I'm attached.
KMRepository
I have a singleton instance which can be accessed by executing the following:
"self default"
I am currently a god object to be refactored =D.
KMRepository>>#reset
World setProperty: #kmDispatcher toValue: nil.
self default: self new.
KMCategory allSubclasses
select: [ :c | c is GlobalCategory ]
thenDo: [ :c | c new installAsGlobalCategory ].
KMPragmaKeymapBuilder uniqueInstance reset.
KMPragmaKeymapBuilder
I am a singleton object, subscribed to system events, to listen to the creation of
methods marked with the <keymap> and keymap:> pragmas.
When I listen one of those events, I reinitialize the KMRepository default instance
and reload it with all declared keymaps.
う〜ん、らちがあかない…
イベントループ
下から追うのが難しそうなので、今度はイベントループを調べてみます。
教えて頂いた WorldState
と、グローバル変数 World
(こちらは WorldMorph
のインスタンス) が関係が深そう。
WorldMorph>>#doOneCycle
worldState doOneCycleFor: self.
WorldState>>#doOneCycleFor: aWorld
self interCyclePause: MinCycleLapse.
self doOneCycleNowFor: aWorld.
WorldState>>#doOneCycleNowFor: aWorld
DisplayScreen checkForNewScreenSize.
LastCycleTime := Time millisecondClockValue.
self handsDo: [:h |
ActiveHand := h.
h processEvents.
ActiveHand := nil
].
ActiveHand := self hands first.
aWorld runStepMethods.
self displayWorldSafely: aWorld.
displayWorldSafely: aWorld
[aWorld displayWorld] ifError: [:err :rcvr |
| errCtx errMorph |
errCtx := thisContext.
[
errCtx := errCtx sender.
....
errMorph hasProperty: #errorOnDraw
] whileTrue.
errMorph setProperty: #errorOnDraw toValue: true.
rcvr error: err.
].
↑今読み返すと WorldState>>#doOneCycleNowFor: aWorld
の中の ActiveHand processEvent
が鍵ぽいのですが、
この時点では読み取れませんでした。
キー入力周り
再び下から、でも目線を変えてキー入力に関係するコードを探してみようと考えました。
Smalltalk なので、メタキーを扱うコードが有るはず… #meta
の sender を見る…
→ SimulateKeystrokesSpecification>>#testSimulateCmdKeystroke
の中に self simulateKeyStrokes: ...
というコードを発見。
Morph>>#simulateKeyStroke: aCharacter
|event|
event := KeyboardEvent new
setType: #keystroke
buttons: 0
position: 0@0
keyValue: aCharacter charCode
charCode: aCharacter charCode
hand: ActiveHand
stamp: 0.
self keyStroke: event
KeyboardEvent
クラス! この Class ref から、 HandMorph>>#generateKeyboardEvent
を見つけました。
後はこれの sender を探すのみ。
HandMorph>>#processEvents
| evt evtBuf type hadAny |
[(evtBuf := Sensor nextEvent) isNil] whileFalse:
[evt := nil.
type := evtBuf first.
type = EventTypeMouse ifTrue: [recentModifiers := evtBuf sixth. evt := self generateMouseEvent: evtBuf].
type = EventTypeKeyboard ifTrue: [recentModifiers := evtBuf fifth. evt := self generateKeyboardEvent: evtBuf].
type = EventTypeDragDropFiles ifTrue: [evt := self generateDropFilesEvent: evtBuf].
type = EventTypeWindow ifTrue: [evt := self generateWindowEvent: evtBuf].
(type ~= EventTypeDragDropFiles and: [evt isNil]) ifTrue: [^self].
evt isNil
ifFalse:
[
self handleEvent: evt.
hadAny := true.
...]].
...
そして HandMorph>>#handleEvent:anEvent
が呼ばれる、のではないか…
github.com
今日たどり着いたのはここまで!
(でもキーマッピングの話はまだ遠いにょ…)
おしまい
これからビールを飲みながら勉強会です。