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)」が宣言されているので、その名前で値を指定する。