Leichtes Skripting – Eclipse Advanced Scripting Environment (EASE)


de KaffeeKlatsch Eclipse

Erschienen im KaffeeKlatsch 11/2018


In grauer Vorzeit, als Emacs noch weiter verbreitet war, konnte der Anwender seinen Editor bzw. seine IDE selbstverständlich durch eigene Skripte einfach erweitern. Einerseits geht es dabei um die Anpassung vorhandener Funktionen aber andererseits um Ad-Hoc Skripte die – mal eben – einmalige aber langwierige Texttransformationen einfacher durchführen. Mit EASE ist das auch in Eclipse wieder easy.

Eclipse – wie auch die anderen großen Java-IDEs – lässt sich über Plugins erweitern. Diese Plugins kann der Anwender aber normalerweise nicht erweitern, sondern nur im Rahmen der vorgesehenen Einstellungen konfigurieren. Eine Plugin übergreifende Anpassung oder gar Automatisierung ist für den Benutzer nicht vorgesehen. Die Hürde für ein eigenes Plugin ist aber hoch:

Wenn das Plugin in einer CI-Pipeline gebaut werden soll, darf man sich zusätzlich mit arkanen Werkzeugen wie Maven-Tycho herumärgern.

Mit EASE1, dem Eclipse Advanced Scripting Environment, geht das deutlich einfacher:

Dabei wird das Skript nicht in einem neuen Prozess ausgeführt, sondern direkt im laufenden Eclipse selbst. Das Skript hat also – über “Module” – Zugriff auf die Datenstrukturen, Editoren, Views und Perspektiven von Eclipse. Liegt das Skript in einem “beobachteten Verzeichnis” kann es sich in Menüs und Toolbars einhängen oder an Tastenkombinationen binden. Als Skript-Sprachen stehen “Engines” u.A. für JavaScript, Groovy, Jython und Jruby bereit.

Installation

Die Installation von EASE erfolgt über die Update-Site. Allerdings sollte man aus dieser Update-Site zuerst nur bestimmte Features installieren. Erstens würden einige der Features sehr viele zusätzliche Plugins benötigen und zweitens arbeiten im Moment nicht alle Plugins sauber mit dem aktuellen Eclipse (SimRel 2018-09) zusammen. Folgende Features sollten ausgewählt werden (siehe Abbildung 1):

Die folgenden zusätzlichen Features schaden ebenfalls nicht:

Abbildung 1: Installation

EASE stellt im Git-Repository “org.eclipse.ease.scripts.git”2 etliche Beispielprojekte und Einführungs-Skripte vor. Insbesondere kann man die erfolgreiche Installation mit dem Skript 2 Hello World UI.js prüfen, indem man im Kontext-Menü der Datei “Run As → EASE Script” auswählt. Es erscheint eine Dialogbox mit “Hello World”.

Beispiel

Eine Aufgabe für Skripting in der IDE sind komplexe Suchen im Quellcode. Als Beispiel: Eine Methode someCall(String c, int i) bekommt als ersten Parameter bisher meist einen String mit einer Annäherung an eine mathematische Konstante wie π oder e. Bisherige Aufrufstellen folgen diesem Schema:

String foo = Constants.PI;
// Weitere Berechnungen
someCall(foo, 42);

Gesucht sind nun alle Aufrufer von someCall, bei denen Constants.PI bis zu drei Zeilen zuvor verwendet wird. Diese Stellen sollen manuell geprüft werden, ob statt PI die bessere Annäherung BETTER_PI verwendet werden soll.

Die IDE-Funktion “Find References” kann entweder nur alle Vorkommen von PI oder alle Aufrufe von someCall finden aber nicht die Kombination von beiden. Eine reine Text-Suche kann das durch Regular Expressions nur, wenn beide Suchbegriffe in derselben Zeile stehen.

Das Vorgehen mittels EASE-Skripts ist einfach:

Anschließend liegen alle Treffer als temporäre Tasks in der Task-View vor und können durch einfaches Klicken nacheinander abgearbeitet werden. Oder ein zweites Skript springt auf Tastendruck zur Position des ersten Tasks und löscht ihn. Der Anwender inspiziert die Codestelle, drückt die Taste erneut und kann so die Liste effizient abarbeiten.

Die vollständigen Skripte sind am Ende des Artikels abgedruckt. Bemerkenswert ist nicht die Logik selbst sondern nur einige Punkte:

Fazit

Die beiden Skripte lösen das Problem, zwei nicht unmittelbar benachbarte Dinge zu finden, mit wenig Code und noch dazu mit komfortabler Einbindung in die IDE. Shell-Skripte könnten dieselben Stellen finden, aber die Integration in die IDE fehlt dann völlig.

EASE biete für viele Dinge komfortable Hilfsmethoden an. Reichen diese nicht aus, dann ist durch den direkten Zugriff auf die Eclipse-Internas keine zusätzliche Hürde – wie zum Beispiel ein erzwungener Sprachwechsel oder zusätzliche Plugins – vorhanden.

Skripting in der IDE ist also wieder möglich. Seltsam, dass man so wenig davon hört!

/**
 * name 		: find-nearby
 * description	: Finde alle Stellen mit "Constant.PI und "someCall("
 * keyboard		: F6
 */
loadModule('/System/Resources');
loadModule('/System/UI');

let dir = showFolderSelectionDialog(null, "Seach Nearby", "zu durchsuchendes Verzeichnis");
if( dir == null )
	exit();

var taskCount = 0;
let javaFiles = findFiles("*.java", dir);
for each (let f in javaFiles){
	searchFile(f);
}
print("OK")
showInfoDialog('Anzahl der erzeugten Tasks: '+taskCount);

function searchFile(file){
	print("open file "+file);
	let handle = openFile(file);
	let lineWithConstant=0;
	let line = readLine(handle);
	for(let lineNumber=1; line != null; ++lineNumber){
		if( line.indexOf("Constants.PI") >= 0 )
			lineWithConstant = lineNumber;

		if( line.indexOf("someCall(") >= 0 ){
			if( lineNumber - lineWithConstant < 3 ){
				markLine(file, lineWithConstant, "Check PI constant");
				++taskCount;
			}
			lineConstant=0;
		}
		line = readLine(handle);
	}

	closeFile(handle);
}

function markLine(file, lineNumber, message){
	print("Adding task for "+file+" line "+lineNumber);
	var marker = file.createMarker(org.eclipse.core.resources.IMarker.TASK);
	marker.setAttribute(org.eclipse.core.resources.IMarker.TRANSIENT, true);
	marker.setAttribute(org.eclipse.core.resources.IMarker.LINE_NUMBER, lineNumber);
	marker.setAttribute(org.eclipse.core.resources.IMarker.MESSAGE, message);
	marker.setAttribute(org.eclipse.core.resources.IMarker.DONE, false);
	marker.setAttribute(org.eclipse.core.resources.IMarker.USER_EDITABLE, true);
}
Skript 1 – `find-nearby.js`
/**
 * name 		: next-task
 * description	: Springe zum ersten Task und lösche ihn.
 * keyboard		: F7
 */
loadModule('/System/Resources');
loadModule('/System/UI');

var ws = getWorkspace();
var markers = ws.findMarkers(
		org.eclipse.core.resources.IMarker.TASK,
		false,
		org.eclipse.core.resources.IResource.DEPTH_INFINITE);

if( markers.length > 0 ){
	let marker = markers[0];

	//BENÖTIGT FREIGABE in Preferences->Scripting: Allow scripts to run code in UI thread
	executeUI(function(){
		let page = org.eclipse.ui.PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
		org.eclipse.ui.ide.IDE.openEditor(page, marker);
	});

	marker.delete();
}
else {
	showErrorDialog("Keine weiteren Tasks mehr vorhanden!")
}
Skript 2 – `next-task.js`

Kurzbiografie

Andreas Heiduk ist als Senior Consultant für MATHEMA Software GmbH tätig. Seine Themenschwerpunkte umfassen Domänenspezifische Sprachen, die Java Standard Edition (JSE) und die Java Enterprise Edition (JEE). Daneben findet er die unterschiedlichsten Themen von hardwarenaher Programmierung bis hin zu verteilten Anwendungen interessant.