Xtextのキーバインドのメモ。
Xtextでのキーバインド(エディター上で入力できるキーの割り当て)方法は、通常のEclipseプラグインのキーバインドと同じ。
だが、例えばF4キーはデフォルトでXtextの「Open Type Hierarchy」が割り当てられているので、F4キーで独自の処理を定義することが出来ない。(キーが競合してしまい、有効にならない)
そこで、自分のエディター専用のキーバインドのスコープ(範囲)を定義し、その中でキーバインドを定義する必要がある。
XtextのF4キーはorg.eclipse.xtext.xbase.ui*.jarのplugin.xmlで定義されている。
(自分のplugin.xmlのDpendenciesにorg.eclipse.xtext.xbase.uiを追加しないと見られない)
<extension point="org.eclipse.ui.commands"> <command description="%OpenTypeHierarchyCommand_description" id="org.eclipse.xtext.xbase.ui.hierarchy.OpenTypeHierarchy" name="%OpenTypeHierarchyCommand_name"> </command> 〜 </extension> <extension point="org.eclipse.ui.bindings"> <key contextId="org.eclipse.xtext.ui.XtextEditorScope" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" sequence="F4" commandId="org.eclipse.xtext.xbase.ui.hierarchy.OpenTypeHierarchy"> </key> 〜 </extension>
contextIdの「org.eclipse.xtext.ui.XtextEditorScope」がXtextのエディターを表すスコープ。
これは以下のように定義されている。
<extension point="org.eclipse.ui.contexts"> <context description="%XtextContext_description" id="org.eclipse.xtext.ui.XtextEditorScope" name="%XtextContext_name" parentId="org.eclipse.ui.textEditorScope"> </context> </extension>
したがって、これらを真似て自分の定義を作ればよい。
キーバインドのスコープを定義したら、自分のエディターとそのスコープのID(contextId)とを結びつける必要がある。
これは、(plugin.xmlの定義ではなく)エディタークラス(Javaソース)から行う。
XtextEditor.java(Xtext2.2以降)には、以下のようにキーバインドのスコープを設定するメソッドが用意されている。
public class XtextEditor extends TextEditor { 〜
/** * @since 2.2 */ public static final String KEY_BINDING_SCOPE = "org.eclipse.xtext.ui.editor.XtextEditor.KEY_BINDING_SCOPE"; /** * @since 2.2 */ public static final String DEFAULT_KEY_BINDING_SCOPE = "org.eclipse.xtext.ui.XtextEditorScope";
private String keyBindingScope;
/** * Note: Not injected directly into field as {@link #initializeKeyBindingScopes()} is called by constructor. * * @since 2.2 */ @Inject(optional = true) public void setKeyBindingScope(@Named(KEY_BINDING_SCOPE) String scope) { if (scope != null) { this.keyBindingScope = scope; initializeKeyBindingScopes(); } }
/** * Set key binding scope. Required for custom key bindings (e.g. F3). */ @Override protected void initializeKeyBindingScopes() { setKeyBindingScopes(new String[] { keyBindingScope != null ? keyBindingScope : DEFAULT_KEY_BINDING_SCOPE }); }
initializeKeyBindingScopesメソッドは、XtextEditorの親クラスであるAbstractDecoratedTextEditorのコンストラクターから呼ばれる。
そのままではデフォルトのDEFAULT_KEY_BINDING_SCOPEが設定される。
で、setKeyBindingScopeメソッドを呼び出してやれば自分独自のスコープを設定することが出来る。
キー定義を行うには、plugin.xmlにキーバインドを定義する。
キーバインドを定義する際にはコマンドのIDが必要。
そして、コマンドを定義する際にはコマンドを実行するハンドラークラスが必要。
したがって、まずハンドラークラスを作成する。
import org.eclipse.core.commands.AbstractHandler; import org.eclipse.core.commands.ExecutionEvent; import org.eclipse.core.commands.ExecutionException; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.text.IRegion; import org.eclipse.jface.text.ITextSelection; import org.eclipse.jface.text.Region; import org.eclipse.jface.text.source.ISourceViewer; import org.eclipse.xtext.nodemodel.ILeafNode; import org.eclipse.xtext.nodemodel.util.NodeModelUtils; import org.eclipse.xtext.parser.IParseResult; import org.eclipse.xtext.resource.XtextResource; import org.eclipse.xtext.ui.editor.XtextEditor; import org.eclipse.xtext.ui.editor.model.IXtextDocument; import org.eclipse.xtext.ui.editor.utils.EditorUtils; import org.eclipse.xtext.util.concurrent.IUnitOfWork;
public class OpenModelHierarchyHandler extends AbstractHandler {
//@Override public Object execute(ExecutionEvent event) throws ExecutionException { XtextEditor xtextEditor = EditorUtils.getActiveXtextEditor(event); if (xtextEditor != null) { ITextSelection selection = (ITextSelection) xtextEditor.getSelectionProvider().getSelection(); IRegion region = new Region(selection.getOffset(), selection.getLength()); ISourceViewer sourceViewer = xtextEditor.getInternalSourceViewer(); EObject object = getEObject(sourceViewer, region); 〜 } return null; }
private EObject getEObject(ISourceViewer textViewer, final IRegion region) { return ((IXtextDocument) textViewer.getDocument()).readOnly(new IUnitOfWork<EObject, XtextResource>() { //@Override public EObject exec(XtextResource resource) throws Exception { IParseResult parseResult = resource.getParseResult(); if (parseResult != null) { ILeafNode leaf = NodeModelUtils.findLeafNodeAtOffset(parseResult.getRootNode(), region.getOffset()); return leaf.getSemanticElement(); } return null; } }); } }
ハンドラー内の実装方法は、org.eclipse.xtext.ui.editor.hyperlinking.OpenDeclarationHandlerクラスが参考になる。
そして、plugin.xmlを記述する。
<!-- キーバインドスコープ(コンテキストID)の定義 --> <extension point="org.eclipse.ui.contexts"> <context description="DMDL EditorX Scope" id="jp.hishidama.xtext.dmdl_editor.DMDLEditorScope" name="Editing DMDL" parentId="org.eclipse.xtext.ui.XtextEditorScope"> </context> </extension>
自分のエディター用のキーバインドのスコープ(コンテキストID)を定義する。
親スコープはXtextEditorのデフォルトのスコープとする。
コンテキストのnameは、Eclipseのキーの設定(メニューバーの「Window」→「Preferences」で設定ダイアログを開き、「General」⇒「Keys」で表示されるキー一覧)に表示される。
<!-- キーバインドの定義 --> <extension point="org.eclipse.ui.bindings"> <key contextId="jp.hishidama.xtext.dmdl_editor.DMDLEditorScope" schemeId="org.eclipse.ui.defaultAcceleratorConfiguration" sequence="F4" commandId="jp.hishidama.xtext.dmdl_editor.hyperlinking.OpenModelHierarchy"> </key> </extension>
キーバインドの定義時に、自分独自のスコープをコンテキストIDに指定する。
<!-- コマンドの定義 --> <extension point="org.eclipse.ui.commands"> <category id="jp.hishidama.xtext.dmdl_editor.commands.category" name="DMDL EditorX"> </category> <command categoryId="jp.hishidama.xtext.dmdl_editor.commands.category" description="Open DataModel Hierarchy" id="jp.hishidama.xtext.dmdl_editor.hyperlinking.OpenModelHierarchy" name="Open DataModel Hierarchy"> </command> </extension>
コマンドを定義する際には、カテゴリーを指定しないと駄目なようだ。
(カテゴリーを定義しないと、Eclipseの設定でキー一覧を表示した際に、定義したはずのキーバインドが表示されないようだ。また、エディター上でそのキーを押しても何も反応しない)
<!-- コンテキストメニューの定義 --> <extension point="org.eclipse.ui.menus"> <menuContribution locationURI="popup:#TextEditorContext?after=group.open"> <command commandId="jp.hishidama.xtext.dmdl_editor.hyperlinking.OpenModelHierarchy" style="push"> <visibleWhen checkEnabled="false"> <reference definitionId="isActiveEditorAnInstanceOfXtextEditor"> </reference> </visibleWhen> </command> </menuContribution> </extension>
<!-- ハンドラークラスの定義 --> <extension point="org.eclipse.ui.handlers"> <handler class="jp.hishidama.xtext.dmdl_editor.ui.view.model_hierarchy.OpenModelHierarchyHandler" commandId="jp.hishidama.xtext.dmdl_editor.hyperlinking.OpenModelHierarchy"> <activeWhen> <reference definitionId="jp.hishidama.xtext.dmdl_editor.DMDL.Editor.opened"> </reference> </activeWhen> </handler> </extension>
最後に、plugin.xmlに記述した自分独自のコンテキストIDをエディターに設定する。
XtextEditorクラスのsetKeyBindingScopeメソッドを呼び出せばいいのだが、Xtextでは(Googleの)guiceを使っているので、インジェクトすることが出来る。
import org.eclipse.xtext.ui.editor.XtextEditor; import com.google.inject.name.Names;
public class DMDLUiModule extends jp.hishidama.xtext.dmdl_editor.ui.AbstractDMDLUiModule { 〜
public void configureKeyBindingScope(com.google.inject.Binder binder) { binder.bind(String.class).annotatedWith(Names.named(XtextEditor.KEY_BINDING_SCOPE)) .toInstance("jp.hishidama.xtext.dmdl_editor.DMDLEditorScope"); } }
@Named(KEY_BINDING_SCOPE)
」が宣言されているので、その名前で値を指定する。