Skip to content

Instantly share code, notes, and snippets.

@lsloan
Forked from gregsh/- IDE Scripting.md
Last active January 18, 2022 02:41
Show Gist options
  • Save lsloan/b0f8f857e370431aab4317ea5d7eb36f to your computer and use it in GitHub Desktop.
Save lsloan/b0f8f857e370431aab4317ea5d7eb36f to your computer and use it in GitHub Desktop.
IDE Scripting for IntelliJ IDEA - See also: https://youtrack.jetbrains.com/issue/IDEA-138252#comment=27-959300

Here are my attempts to script an IntelliJ-based IDE.

IDE Scripting Console is backed by JSR-223 (javax.script.*) API.

Groovy, Clojure, JavaScript and other scripting languages may be used.

Open IDE Scripting Console, type a statement, hit Ctrl-Enter to execute the current line or selection.

.profile.language-extension file in the same directory will be executed along with it if present.

CAUTION: This is a real stuff! Try System.exit(0) once to get the idea that any damage is possible.

(doseq [x (list (.project IDE) (.application IDE))]
(let [pico (.getPicoContainer x)
field (.getDeclaredField (.getClass pico) "componentKeyToAdapterCache")
services (do (.setAccessible field true) (.get field pico))]
(doseq [key (filter #(instance? String %) (.keySet services))]
(let [match (re-find (re-matcher #"[\.]([^\.]+?)(Service|Manager|Helper|Factory)?$" key))
groups (rest match)
singular (empty? (rest groups))
words (seq (com.intellij.psi.codeStyle.NameUtil/nameToWords (first groups)))
short0 (clojure.string/join (flatten (list (.toLowerCase (first words)) (rest words))))
shortName (if (false? singular) (com.intellij.openapi.util.text.StringUtil/pluralize short0) short0)]
(try
(intern *ns* (symbol shortName) (.getComponentInstance pico key))
(catch Exception e ()))
)
)
)
)
(defn file [^String x] (.findFileByUrl virtualFiles (com.intellij.openapi.vfs.VfsUtil/pathToUrl x)))
(defn file2 [^String x] (first (filter #( = x (.getName %)) (.getOpenFiles fileEditors))))
(defn all-scope [] (com.intellij.psi.search.GlobalSearchScope/allScope (.project IDE)))
(defn findPsi [^String x] (com.intellij.psi.search.FilenameIndex/getFilesByName (.project IDE), x, (all-scope)))
(defn findFile [^String x] (com.intellij.psi.search.FilenameIndex/getVirtualFilesByName (.project IDE) x, (all-scope)) )
(defn firstPsi [^String x] (first (findPsi x)))
(defn firstFile [^String x] (first (findFile x)))
(defn ep [^String x] (.getExtensionPoint (com.intellij.openapi.extensions.Extensions/getArea nil) x))
(defn psi [x] (.findFile psis x))
(defn document [x] (.getDocument fileDocuments x))
(defn editor ([x] (.getEditor (.getSelectedEditor fileEditors x)))
([] (try (.. windows (getFocusedComponent (. IDE project)) (getEditor)) (catch Exception e ()) )) )
(defn #^Runnable runnable [x] (proxy [Runnable] [] (run [] (x))))
(defn write [x] (.. IDE application (runWriteAction (runnable x))))
(defn read [x] (.. IDE application (runReadAction (runnable x))))
(defn pool [x] (.. IDE application (executeOnPooledThread (runnable x))))
(defn swing [x] (com.intellij.util.ui.UIUtil/invokeLaterIfNeeded (runnable x)))
(defn dumpe [e] (.print IDE (str e (clojure.string/replace (str (seq (.getStackTrace e))) #"#<StackTraceElement " "\n at "))))
(defn safe [x] (try (x) (catch Exception e (dumpe e))))
(defn data-key [e] (let [c ['com.intellij.openapi.actionSystem.LangDataKeys]]
(some #(try (. (. (Class/forName (str %)) (getField (str e))) (get nil)) (catch Exception e nil) ) c)))
(defn event-data [e key] (.getData (data-key key) (.getDataContext e)))
(defn action [name & x]
(.unregisterAction actions name)
(.. keymaps getActiveKeymap (removeAllActionShortcuts name))
(if (nil? (second x)) nil
(let [[shortcut, perform] x]
(. actions (registerAction name (proxy [com.intellij.openapi.actionSystem.AnAction] [name, name, nil]
(actionPerformed [e] (perform e)) )))
(if (= shortcut nil) nil
(.. keymaps (getActiveKeymap) (addShortcut name
(new com.intellij.openapi.actionSystem.KeyboardShortcut (javax.swing.KeyStroke/getKeyStroke shortcut) nil)) )
)
nil
)
)
)
(defn dispose [x] (let [t (if (instance? com.intellij.openapi.Disposable x) (x) (.put IDE x nil))]
(if (nil? t) nil (com.intellij.openapi.util.Disposer/dispose t)) ) )
(defn timer [name & x] (do
(dispose name)
(if (nil? (second x)) nil
(let [[delay, perform] x
h (new com.intellij.util.Alarm (.project IDE))
r (runnable (fn time_fun [] (do (perform) (. h (addRequest (runnable time_fun) delay)) ) ))]
(. h (addRequest r delay))
(. IDE (put name h))
)
) ))
import static com.intellij.openapi.actionSystem.LangDataKeys.*
//////////////////////////////////////////////////////////////////////////////////////////////
metaClass.propertyMissing = {name ->
switch (name) {
case "application": return com.intellij.openapi.application.ApplicationManager.getApplication()
case "project": return com.intellij.openapi.project.ProjectManager.getInstance().getOpenProjects()[0]
case "INDEX": return com.intellij.psi.search.FilenameIndex
case "GSS": return com.intellij.psi.search.GlobalSearchScope
case "EXT": return com.intellij.openapi.extensions.Extensions
default:
def variants = []
for (t in [IDE.project, IDE.application]) {
for (obj in t.picoContainer.componentKeyToAdapterCache.keySet()) {
def key = obj instanceof String ? obj : obj instanceof Class ? obj.getName() : null
if (key == null) continue
def match = key =~ /[\.]([^\.]+?)(Service|Manager|Helper|Factory)?$/
def groups = match.size()? match[0][1..-1] : [key, null]
def singular = groups[1..-1][0] == null
def words = com.intellij.psi.codeStyle.NameUtil.nameToWords(groups[0])
def short0 = [words[0].toLowerCase(), words.length==1? "" : words[1..-1]].flatten().join()
def shortName = singular? short0 : com.intellij.openapi.util.text.StringUtil.pluralize(short0)
if (shortName.equals(name)) return t.picoContainer.getComponentInstance(obj);
if (com.intellij.openapi.util.text.StringUtil.containsIgnoreCase(groups[0], name)) variants.add(shortName)
}
}
throw new MissingPropertyException("Service or Component '$name' not found. Variants: $variants")
}
}
String.class.metaClass.file = {-> virtualFiles.findFileByUrl(com.intellij.openapi.vfs.VfsUtil.pathToUrl(delegate))}
String.class.metaClass.file2 = {-> def name = delegate; fileEditors.getOpenFiles().find {file -> file.getName().equals(name) }}
String.class.metaClass.findPsi = {-> INDEX.getFilesByName(project, delegate, GSS.allScope(project)) }
String.class.metaClass.findFile = {-> INDEX.getVirtualFilesByName(project, delegate, GSS.allScope(project)) }
String.class.metaClass.firstPsi = {-> delegate.findPsi()[0] }
String.class.metaClass.firstFile = {-> delegate.findFile()[0] }
String.class.metaClass.ep = {-> EXT.getArea(null).getExtensionPoint(delegate) }
com.intellij.openapi.actionSystem.DataKey.class.metaClass.from = {e -> delegate.getData(e.getDataContext()) }
def virtualFileMetaClass = com.intellij.openapi.vfs.VirtualFile.class.metaClass
virtualFileMetaClass.psi = {-> psiManager.findFile(delegate)}
virtualFileMetaClass.document = {-> fileDocuments.getDocument(delegate)}
virtualFileMetaClass.editor = {-> fileEditors.getSelectedEditor(delegate)?.getEditor()}
def psiMetaClass = com.intellij.psi.PsiElement.class.metaClass
psiMetaClass.document = {-> psiDocuments.getDocument(delegate)}
psiMetaClass.file = {-> delegate.getContainingFile().getVirtualFile()}
psiMetaClass.editor = {-> fileEditors.getSelectedEditor(delegate.file())?.getEditor()}
write = { c -> application.runWriteAction(c)}
read = { c -> application.runReadAction(c)}
pool = { c -> application.executeOnPooledThread(c)}
swing = { c -> com.intellij.util.ui.UIUtil.invokeLaterIfNeeded(c)}
action = { name, shortcut = null, perform = null ->
actions.unregisterAction(name)
keymaps.getActiveKeymap().removeAllActionShortcuts(name)
if (perform == null) return
actions.registerAction(name, new com.intellij.openapi.actionSystem.AnAction(name, name, null) {
@Override
void actionPerformed(com.intellij.openapi.actionSystem.AnActionEvent e) {
perform(e)
} })
if (shortcut != null) {
keymaps.getActiveKeymap().addShortcut(name, new com.intellij.openapi.actionSystem.KeyboardShortcut(
javax.swing.KeyStroke.getKeyStroke(shortcut), null))
}
}
timer = {name, delay = 1, perform = null ->
dispose(name)
if (perform == null) return
def h = new com.intellij.util.Alarm(project)
def r = new Runnable() { public void run() {perform(); h.addRequest(this, delay); }}
h.addRequest(r, delay)
IDE.put(name, h)
}
dispose = { h ->
t = h instanceof com.intellij.openapi.Disposable ? h : IDE.put(h, null)
if (t != null) com.intellij.openapi.util.Disposer.dispose(t)
}
editor = { -> try {windows.getFocusedComponent(project).getEditor()} catch(e){}}
// I have no idea whether this works. It may be DANGEROUS for all I know!
//
// As everybody is remarking, this is actually a rather painful process
// as there are very few examples of how to gather the needed objects
// (editors, files, and so on). Here is an example that renames class
// member variables to follow a typical naming convention (private
// members start with _, static ones with s and static final ones in
// all caps). The IntelliJ codebase itself exhibits considerable
// maturity in not enforcing these tiresome rules which serve to
// inconvenience one of the most useful ways of modifying code in an
// object orientated fashion (i.e. splitting up a function into
// multiple functions inside a new class, and sharing commonly used
// variables as fields in the class).
//
// From comments in upstream gist:
// https://gist.github.com/gregsh/b7ef2e4ebbc4c4c11ee9#gistcomment-1713525
import com.intellij.openapi.project.ProjectManager
import static com.intellij.psi.codeStyle.NameUtil.*
action("Apply naming conventions to class fields", "alt shift O") { e ->
actionEvent = e as AnActionEvent
IDE.print("hello " + actionEvent.dataContext + "\n")
psiFile = PSI_FILE.from(e)
write {
psiFile.classes.each { klass ->
IDE.print("Renaming fields in " + klass + "\n")
klass.allFields.each { field ->
target = field.name
if (field.hasModifierProperty(PsiModifier.STATIC) && field.hasModifierProperty(PsiModifier.FINAL)) {
target = NameUtil.splitNameIntoWords(field.name).join("_").toUpperCase()
}
else if (field.hasModifierProperty(PsiModifier.STATIC)) {
if (field.name[0] != "s") {
target = "s" + field.name
}
}
else if (field.name[0] != "_") {
target = "_" + field.name
}
if (field.name == target) return
IDE.print("renaming " + field.name + " " + field.modifierList + " -> " + target + "\n")
try {
refactor = JavaRefactoringFactory.getInstance(project).createRename(field, target)
// unfortunately, disabling the interactive setting requires that non-code and comment usages
// must be left alone
refactor.setPreviewUsages(false)
refactor.searchInNonJavaFiles = false
refactor.searchInComments = false
refactor.setInteractive(null)
refactor.run()
}
catch (Exception ex) {
IDE.print(ex + "\n")
}
}
}
}
}
(write #(. (document (firstFile "PsiManager.java")) (setText "Rollback!")))
(pool #(do (Thread/sleep 5000) (. IDE print "123")))
(action "TryMe!" "alt shift P" (fn [e] (let
[e (event-data e 'PSI_ELEMENT)
res (and (-> e nil? not) (.canFindUsages finds e)) ]
(. IDE print (str "can find usages of '" e "'? " (if res "Yes!" "Nope"))))))
(timer "bump" 500 #(. IDE (print (str (System/currentTimeMillis) ": bump!"))))
write { "PsiManager.java".firstFile().document().setText("Rollback!") }
pool { Thread.sleep(5000); IDE.print("123") }
action("TryMe!", "alt shift P") { e->
e = PSI_ELEMENT.from(e)
res = e != null && finds.canFindUsages(e)
IDE.print("can find usages of '" + e +"'? " + (res ? "Yes!" : "Nope"))
}
timer("bump", 500) {
IDE.print(System.currentTimeMillis() + ": bump!")
}
dispose("bump")
def ep = "com.intellij.iconLayerProvider".ep()
ep.registerExtension(new com.intellij.ide.IconLayerProvider() {
String getLayerDescription() { return "123" }
javax.swing.Icon getLayerIcon(com.intellij.openapi.util.Iconable element, boolean isLocked) {
"config".equals(element.getName()) ? com.intellij.icons.AllIcons.Nodes.FinalMark : null
}
})
ep.unregisterExtension(ep.getExtensions().last())
@michaelfward
Copy link

Do you happen to have any links / sources for the API exposed by the IntelliJ IDEs?

@SamB
Copy link

SamB commented Oct 28, 2019

@michaelfward

Do you happen to have any links / sources for the API exposed by the IntelliJ IDEs?

Well, you could look at the IntelliJ Platform SDK docs and also the IntelliJ IDEA Community Edition source. (Best viewed in IntelliJ, naturally.)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment