S-JIS[2014-02-25] 変更履歴

Xtextキーバインド

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を追加しないと見られない)

org.eclipse.xtext.xbase.ui*.jar/plugin.xml:

    <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のエディターを表すスコープ。
これは以下のように定義されている。

org.eclipse.xtext.ui*.jar/plugin.xml:

   <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メソッドを呼び出してやれば自分独自のスコープを設定することが出来る。


独自のF4キーを定義する例

キー定義を行うには、plugin.xmlにキーバインドを定義する。
キーバインドを定義する際にはコマンドのIDが必要。
そして、コマンドを定義する際にはコマンドを実行するハンドラークラスが必要。

したがって、まずハンドラークラスを作成する。

uiプロジェクト/〜/OpenModelHierarchyHandler.java:

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を記述する。

uiプロジェクト/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を使っているので、インジェクトすることが出来る。

uiプロジェクト/〜/DMDLUiModule.java:

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");
	}
}

Xtext目次へ戻る / Eclipseへ戻る / 技術メモへ戻る
メールの送信先:ひしだま