MPS 2019.2ヘルプ

HowTo-追加のツールを追加する (別名再生)

追加のツールを追加する (別名再生)

この文書では新しいツールを構築する方法を説明します(Eclipseの場合
ユーザー、MPSのツールはMPSのEclipseのビューに似ています。これは
MPSに任意の追加のツールを組み込む例。このテキストは
ツール開発の側面:言語とメニューに新しいツールを追加する方法
システム、および現在編集されているものとビューを同期させる方法。

他のすべての点でツールは単なるSwing UIプログラムです。実装する
洗練されたビュー、Java Swingの経験が必要です。このチュートリアルでは
Swingでツリービューを作成することの複雑さにあまり焦点を当てないでください。
それは意外にも簡単なことです。

アウトラインツール自体

MPSプラグイン言語は新しいツールの定義をサポートします。新しいを作成します
Tool のインスタンスと名前を設定します。また別のものを与えることができます
キャプションとアイコン。デフォルトの場所(下、左、右、上)も
定義済み。

ここで、ツールに3つのフィールドを追加します。1つ目はプロジェクトを記憶するために使用され、2つ目はツールをMPSのメッセージ・バスに接続し、3つ目はアクティブなエディターが変更された場合に ToolSynchronizer. The ToolSynchronizer がアウトライン・ビューを更新することを記憶します。メッセージ・バスは、イベント配信用のIDEAプラットフォームのインフラストラクチャーです。現在アクティブなエディターでの選択の変更の通知を受け取るために使用します。この2つについては後で詳しく説明します。

private Project project; private MessageBusConnection messageBusConn; private ToolSynchronizer synchronizer;

これで、ツールの3つの主要メソッドを実装する準備が整いました: init
dispose and getComponentこれがコードです(コメント付き、どうぞ
読みます! ):

init(project)->void { this.project = project; } dispose(project)->void { // disconnect from message bus -- connected in getComponent() if ( this.messageBusConn != null ) this.messageBusConn.disconnect(); } getComponent()->JComponent { // create a new JTree with a custom cell renderer (for rendering custom icons) JTree tree = new JTree(new Object[0]); tree.setCellRenderer(new OutlineTreeRenderer(tree.getCellRenderer())); // create a new synchronizer. It needs the tree to notify it of updates. this.synchronizer = new ToolSynchronizer(tree); // connect to the message bus. The synchronizer receives editor change events // and pushes them on to the tree. The synchronizer implements // FileEditorChangeListener to be able to make this work this.messageBusConn = this.project.getMessageBus().connect(); this.messageBusConn.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this.synchronizer); // we finally return a ScrollPane with the tree in it return new JScrollPane(tree); }

ツールを開くためのアクション

アクションはMPS UIにあるコマンドです。開くためのアクションを追加する必要があります
アウトラインビューアークションは言語のプラグインの側面にあります。アクションは
予想通り、キーボードショートカット、キャプション、およびアイコンを定義します。彼らはまた宣言する
アクションパラメータこれらは可能になるためにどのコンテキストが利用可能である必要があるかを定義します
アクションを実行します。これはメニュー内のアクションの存在を決定します。
そして、その文脈を行動そのものに伝えることをサポートします。私たちの場合
コンテキストには、現在開かれているプロジェクトと現在開かれているプロジェクトが含まれます。
ファイルエディター

action context parameters ( always visible = false ) Project project key: PROJECT required FileEditor editor key: FILE_EDITOR required

アクションの execute メソッドで、アウトラインツールを作成して開きます。
setOutline メソッドはそれをアウトラインツールとして使う普通のメソッドです
itseld。渡されたエディターを単に保存するだけです。

execute(event)->void { tool<Outline> outline = this.project.tool<Outline>; outline.setEditor(this.editor); outline.openTool(true); }

アクショングループ

メニュー項目はグループを介して追加されます。にアウトラインを開くアクションを追加できるようにするには
メニューシステムでは、新しいグループを定義する必要があります。グループはその内容を定義します
(アクション、および2つの区切り記号)そしてそれは、
メニューは行くべきです。グループも同様にプラグインの側面に住んでいます。

group OutlineGroup is popup: false contents <---> OpenOutlineAction <---> modifications add to Tools at position customTools

ツールのライフサイクルを管理する

このツールは、MPSの他の部分とうまく連携する必要があります。それは多くのを聞いている必要があります
イベントと適切に反応します。このタスク専用のリスナーは2人います。の
EditorActivationListener は潜在的に多くのオープンエディターのどれを追跡します
アウトラインビューでは、どんな場合でもアウトラインを表示する必要があるため、現在アクティブです。
エディターは現在ユーザーによって使用されています。フックアップすることも責任があります
選択状態とモデルの変更を扱う他のリスナー(参照
以下)。 ModelLifecycleListener はモデルのライフサイクルイベントを追跡します
それは現在アクティブなエディターによって編集されます。モデルは交換することができます
たとえば、変更を元に戻すことで編集されます。

エディターのアクティブ化とフォーカス

EditorActivationListener は潜在的に多くのオープンのどれを追跡します
エディターは現在アクティブです: インスタンス化され、MPSメッセージにフックされます
次のコードで作成されたツールによるバス(すでに上記のとおり):

this.messageBusConn = this.project.getMessageBus().connect(); this.messageBusConn.subscribe(FileEditorManagerListener.FILE_EDITOR_MANAGER, this.synchronizer);

そのコンストラクターでは、アウトラインツリーを覚えています。それからそれは新しいアウトラインを設定します
ユーザーによる選択の変更を待機するツリー選択リスナー
木の中に。

this.outlineSelectionListener = new OutlineSelectionListener(this); tree.addTreeSelectionListener(this.outlineSelectionListener);

その後、モデルのライフサイクル(ロード、アンロード、ロード)を追跡するためのリスナーを設定します。
置き換えます)そしてそれはイベントコレクタ(両方とも以下で説明される)と接続します。

modelLifecycleListener = new ModelLifecycleListener(tree); eventsCollector = new ModelChangeListener(tree);

EditorActivationListener implements FileEditorManagerListenerなので
以下の3つのメソッドを実装する必要があります。

void fileOpened(FileEditorManager p0, VirtualFile p1); void fileClosed(FileEditorManager p0, VirtualFile p1); void selectionChanged(FileEditorManagerEvent p0);

場合は、selectionChanged イベントに興味があります。
他のすべての種類のリスナーをクリーンアップして接続する必要があります。これが
実装。

public void selectionChanged(FileEditorManagerEvent event) { // read action is required since we access the model read action { FileEditor oldEditor = event.getOldEditor(); // grab the old editor and clean it up if there is one if (oldEditor != null) { this.cleanupOldEditor(oldEditor); } // call a helper method that sets up the new editor this.newEditorActivated(event.getNewEditor()); } }

cleanupOldEditor メソッドは古いものから既存のリスナーを削除します:
非アクティブエディター

private void cleanupOldEditor(FileEditor oldEditor) { // Downcast from IDEA level to MPS specifics and // grab the NodeEditor component IEditor oldNodeEditor = ((MPSFileNodeEditor) oldEditor).getNodeEditor(); if (oldNodeEditor != null && this.editorSelectionListener != null) { // remove the selection listener from the old editor oldNodeEditor.getCurrentEditorComponent().getSelectionManager(). removeSelectionListener(this.editorSelectionListener); // grab the descriptor of the model edited by the old editor // and remove the model listener (cleanup!) SModelDescriptor descriptor = oldNodeEditor.getEditorContext().getModel(). getModelDescriptor(); descriptor.removeModelListener(modelLifecycleListener); // remove the model edited by the old editor from the events collector // ...we are not interested anymore. eventsCollector.remove(descriptor); } }

次のメソッドはインフラストラクチャーを新しく選択されたエディターとそれに接続します
基礎となるモデル。保管しているときはいつでも SNodePointer を使用する方法に注意してください
ノードへの参照これはプロキシとして機能し、でノードの解決を処理します。
モデルの場合は置き換えられ、実際のノードへの "弱い参照"が含まれます。
そのため、モデルがアンロードされると、ガベージコレクションが発生する可能性があります。これは重要です
メモリリークを避けてください。

public void newEditorActivated(FileEditor fileEditor) { if (fileEditor != null) { // remember the current editor this.currentEditor = ((MPSFileNodeEditor) fileEditor); // grab the root node of that new editor... SNode rootNode = this.currentEditor.getNodeEditor(). getCurrentlyEditedNode().getNode(); // ...wrap it in an SNodePointer... SNodePointer treeRoot = new SNodePointer(rootNode); // and create a new outline tree model OutlineModel model = new OutlineModel(treeRoot); tree.setModel(model); // create a new selection listener and hook it up // with the newly selected editor this.editorSelectionListener = new EditorSelectionListener(tree, outlineSelectionListener); SelectionManager selectionManager = this.currentEditor.getNodeEditor(). getCurrentEditorComponent().getSelectionManager(); selectionManager.addSelectionListener(this.editorSelectionListener); // This is needed to detect reloading of a model ((MPSFileNodeEditor) fileEditor).getNodeEditor(). getEditorContext().getModel().getModelDescriptor(). addModelListener(modelLifecycleListener); eventsCollector.add(this.currentEditor.getNodeEditor(). getEditorContext().getModel().getModelDescriptor()); } else { tree.setModel(new OutlineModel(null)); } }

モデルライフサイクルの追跡

提供されている ModelLifecycleListener extends the SModelAdapter クラス
MPSによる。モデルの置き換えに興味を持っています。
modelReplaced 法現在保持されているモデルが
たとえば、VCSの反転操作中に新しいものと交換された。

実装では、同じルートに対して新しい新しいツリーモデルを作成します。しながら
このコードはちょっと無意味に見えますが、SNodePointer を使っていることに注意してください。
内部的には自動的に新しいモデルのプロキシされたノードを再解決します。
また、通知を受ける新しいモデルの記述子にリスナーとして自分自身を追加します。
サブモデルのモデル交換イベント

@Override public void modelReplaced(SModelDescriptor descriptor) { tree.setModel(new OutlineModel(((OutlineModel) tree.getModel()).getRealRoot())); descriptor.addModelListener(this); }

ノード選択の同期

トラッキングエディターの選択

これはツリーの選択(および展開状態)を次のように更新します。
現在アクティブなエディターで選択されているノードが変更されます。私達は既に確かめました
(上)アウトラインビューのツリーが現在アクティブなツリーと同期していること
エディター。

クラスはMPS ' SingularSelectionListenerAdapter を拡張したものです。
単一ノードの選択にのみ関心があります。上書きします
次のように selectionChangedTo メソッド:

protected void selectionChangedTo(EditorComponent component, SingularSelection selection) { // do nothing is disabled -- prevents cyclic, never ending updates if (disabled) { return; } // read action, because we access the model read action { // gran the current selection SNode selectedNode = selection.getSelectedNodes().get(0); // ... only if it has changed... if (selectedNode != lastSelection) { lastSelection = selectedNode; // disable the tree selection listener, once again to prevent cyclic // never ending updates treeSelectionListener.disable(); // select the actual node in the tree tree.setSelectionPath(((OutlineModel) tree.getModel()). gtPathTo(selectedNode)); treeSelectionListener.enable(); } } }

モデル構造の変化を追跡する

ノードが追加された場合は、アウトラインビューのツリー構造を更新する必要があります。
エディターで変更(名前変更)、移動、または削除された。ツリー変更イベントは
非常にきめ細かく、オーバーヘッドを避けるために、MPSは関連するバッチにまとめます。
より粒度の高いコマンド。MPS ' EventsCollector 基本クラスを使用することで、
大量のイベントが発生したときに通知を受け取ることができます。
イベントリストを調べて、訪問者を使用したいと思っているものを探します。

ModelChangeListener がこの作業を行います。そのためには、eventsHappened メソッドを実装する必要があります。イベントのリストを取得し、SModelEventVisitorAdapter を拡張してイベントにアクセスし、関心のあるイベントに反応するクラスを使用します。

protected void eventsHappened(List<SModelEvent> list) { super.eventsHappened(list); foreach evt in list { evt.accept(new ModelChangeVisitor()); } }

通知する訪問者として機能する ModelChangeVisitor 内部クラス
ツリーは、visitPropertyEvent をオーバーライドして、
現在のバッチでプロパティーが変更されました: それはそれからすべてのリスナーに通知します
木モデル

public void visitPropertyEvent(SModelPropertyEvent event) { OutlineModel outlineModel = ((OutlineModel) tree.getModel()); foreach l in outlineModel.getListeners() { l.treeNodesChanged(new TreeModelEvent(this, outlineModel.getPathTo(event.getNode()))); } }

それはまた子供の通知を受けるために visitChildEvent を上書きします
ノードの追加/削除 JTree のAPIがちょっとだけであることを除いて
面倒なことに、次のコメント付きコードはそれが何をするのかについてはっきりしているはずです。

@Override public void visitChildEvent(SModelChildEvent event) { // grab the model OutlineModel outlineModel = ((OutlineModel) tree.getModel()); // we need the following arrays later for the JTree API Object[] child = new Object[]{event.getChild()}; int[] childIndex = new int[]{event.getChildIndex()}; // we create a tree path to the parent notify all listeners // of adding or removing children TreePath path = outlineModel.getPathTo(event.getParent()); if (path == null) { return; } // notify the tree model's listeners about what happened foreach l in outlineModel.getListeners() { if (event.isAdded()) { l.treeNodesInserted(new TreeModelEvent(this, path, childIndex, child)); } else if (event.isRemoved()) { l.treeNodesRemoved(new TreeModelEvent(this, path, childIndex, child)); } } }

帰り道: 追跡木の選択

JTreeの選択を追跡することはSwingのを実装することによって起こります
TreeSelectionListener and overwriting it's valueChanged メソッド
次の方法

public void valueChanged(TreeSelectionEvent event) { // don't do anything if disabled --- preventing cyclic updates! if (!(disableEditorUpdate)) { JTree tree = ((JTree) event.getSource()); if (editorActivationListener.currentEditor != null && tree.getLastSelectedPathComponent() instanceof SNodePointer) { // grab the selected treee node SNodePointer pointer = ((SNodePointer) tree. getLastSelectedPathComponent()); // disable the editor selection listener to prevent // cyclic, never ending updates editorActivationListener.editorSelectionListener.disable(); // update the selection in the editor editorActivationListener.currentEditor.getNodeEditor(). getCurrentEditorComponent().selectNode(pointer.getNode()); editorActivationListener.editorSelectionListener.enable(); } } }

Swingのアーティファクト: ツリーモデルとレンダラ

このセクションでは、MPS特有の興味深い側面をいくつか説明します:
Swingアーティファクトの実装

ツリーセルレンダラー

ツリーセルレンダラーは、ツリー内のセルをレンダリングします。それ
getPresentation method on the nodes, and the IconManager を使用して
それぞれのノードのアイコン(のキャッシュ版)を取得します。

public Component getTreeCellRendererComponent(JTree tree, Object object, boolean selected, boolean expanded, boolean leaf, int row, boolean hasFocus) { if (object instanceof SNodePointer) { string presentation; DefaultTreeCellRenderer component; read action { SNode node = ((SNodePointer) object).getNode(); presentation = node.getPresentation(); component = ((DefaultTreeCellRenderer) renderer.getTreeCellRendererComponent( tree, presentation, selected, expanded, leaf, row, hasFocus)); component.setIcon(IconManager.getIconFor(node)); } } else { return renderer.getTreeCellRendererComponent(tree, object, selected, expanded, leaf, row, hasFocus); } }

木モデル

木にそれらの子供たちだけを含むため木モデルは興味深いです
概念に注釈が付けられているノード
ShowConceptInOutlineAttribute 属性それはに格納されています
storeInOutline のロールツリーモデルでは、次の子をフィルタリングする必要があります。
この属性が存在するノードこれがそれぞれのヘルパーメソッドです。

private List findAllChildrenWithAttribute(SNodePointer pointer) { List result = new ArrayList(); SNode node = pointer.getNode(); if (node == null) { return new ArrayList(); } foreach child in node.getChildren() { SNode attribute = AttributeOperations.getNodeAttribute( child.getConceptDeclarationNode(), "showInOutline"); if (attribute != null) { result.add(child); } } return result; }
最終更新日: 2019年8月30日