Skip to content

Instantly share code, notes, and snippets.

Embed
What would you like to do?
IDE Scripting

Here are my attempts to script an IntelliJ-based IDE using javax.script.* API (ex-JSR-223).

The list of available scripting languages and engines:

  1. Groovy - built-in, via Groovy jars and <app>/lib/groovy-jsr223-xxx.jar
  2. JavaScript (Nashorn) - built-in, via Java Runtime <app>/jbr/... (deprecated and will be removed soon)
  3. JavaScript (GraalJS) - https://plugins.jetbrains.com/plugin/12548-intellij-scripting-javascript
  4. JPython - https://plugins.jetbrains.com/plugin/12471-intellij-scripting-python
  5. JRuby - https://plugins.jetbrains.com/plugin/12549-intellij-scripting-ruby
  6. Clojure - https://plugins.jetbrains.com/plugin/12469-intellij-scripting-clojure
  7. Kotlin - https://plugins.jetbrains.com/plugin/6954-kotlin (bundled in IntelliJ IDEA)

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: Scripts exert full control over IDE VM so any damage is possible, e.g. System.exit(0).

(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())
(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){}}
import com.intellij.openapi.ui.Messages
val b = bindings as Map<String, Any>
val IDE = b["IDE"] as com.intellij.ide.script.IDE
val name = System.getProperty("user.name")
IDE.print("$name>")
Messages.showInfoMessage("Hi, $name", IDE.project.name)
import com.intellij.notification.*
// Change the `pattern` and the `replacement` as needed
def pattern = ~/(.*)XXX\.(.*)/
def replacement = "\$1_xxx_.\$2"
// `Rename Multiple Files` action:
// 1. select some files in `Project` view
// 2. invoke it via Search Everywhere/Goto Action or a shortcut
// 3. see `Event Log` for the report
// Note: `action`, `write`, etc. are defined in `.profile.groovy`
action("Rename Multiple Files", "control alt R") { event ->
def files = VIRTUAL_FILE_ARRAY.getData(event.getDataContext())
def report = new StringBuilder()
for (def file : files) {
def curName = file.name
def matcher = curName =~ pattern
if (!matcher.matches()) {
report.append("$curName skipped\n")
}
else {
def newName = matcher.replaceFirst(replacement)
report.append("$curName -> $newName\n")
write {
file.rename(this, newName)
}
}
}
Notifications.Bus.notify(new Notification(
"test",
"${event.presentation.text}: ${files ? files.length : 0} file(s) processed",
report.toString(), NotificationType.INFORMATION))
}
@hellboy81

This comment has been minimized.

Copy link

@hellboy81 hellboy81 commented Apr 22, 2015

JavaScript samples wanted

@afaucogney

This comment has been minimized.

Copy link

@afaucogney afaucogney commented May 26, 2015

JS would be also good yes

@indigodomo

This comment has been minimized.

Copy link

@indigodomo indigodomo commented Jul 30, 2015

How about some simple AppleScript examples of getting the name/path to the current project...

@marcosscriven

This comment has been minimized.

Copy link

@marcosscriven marcosscriven commented Jul 31, 2015

Tried the Groovy example:

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

But got this error:

MissingMethodException: No signature of method: org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.action() is applicable for argument types: (java.lang.String, java.lang.String, Script1$_run_closure1) values: [TryMe!, alt shift P, Script1$_run_closure1@1e21e437]
Possible solutions: notify(), wait()

I'm presuming this example should add a right-click context menu called TryMe!, which finds usages of the token selected?

@MacDada

This comment has been minimized.

Copy link

@MacDada MacDada commented Aug 25, 2015

Where do I find any documentation of the API? I guessed I could use IDE.print("whatever"); from the examples above, but how do I find other object/variables/classes/etc that I could use?

> typeof IDE;
=> object
> Object.keys(IDE);
ECMAException: TypeError: com.intellij.execution.console.RunIdeConsoleAction$IDE@5474feca is not an Object

Schrödinger's IDE: it is both an object and not an object at the same time…

@TonyFlury

This comment has been minimized.

Copy link

@TonyFlury TonyFlury commented Oct 16, 2015

Can you script add ons for the IDE in python ?

@jeshan

This comment has been minimized.

Copy link

@jeshan jeshan commented Nov 16, 2015

No, only Groovy, Clojure and ECMAScript at the moment

@vii

This comment has been minimized.

Copy link

@vii vii commented Mar 3, 2016

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).

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")
        }
      }
    }
  }
}
@coodix

This comment has been minimized.

Copy link

@coodix coodix commented Jun 17, 2016

Please add AppleScript examples

@szkrd

This comment has been minimized.

Copy link

@szkrd szkrd commented Jul 22, 2016

@Codix I doubt that will happen. Languages here are supported by java script context, AppleScript is not one of those (since it hasn't been ported to the jvm). Jython and JRuby may be possible, but not out of the box afaik - though I might be wrong, I'm more of a javascript developer (used rhino excessively).

@lsloan

This comment has been minimized.

Copy link

@lsloan lsloan commented Aug 25, 2016

Am I the only one to see an almost unbearable delay when running the Groovy examples? Anything with more than a couple of lines (adding the .profile.groovy file to my system seems to be my downfall), causes a long, uninterruptible delay before any output appears. Now that I've added .profile.groovy, I've even gotten the Spinning Beachball of Death™.

@ayubmalik

This comment has been minimized.

Copy link

@ayubmalik ayubmalik commented Oct 11, 2017

Where are the docs on what API/DSL is available?
Any chance we could have an example on how to call the "find in path" IDE function in groovy?
I would like to script searching for a given string in all files,
pseudo code:

def searchQry = "My search String"
def result = findInPath(searchQry, ".java") 

Thanks!

@liuyibo123

This comment has been minimized.

Copy link

@liuyibo123 liuyibo123 commented Jan 2, 2018

example for javascript please

@liuyibo123

This comment has been minimized.

Copy link

@liuyibo123 liuyibo123 commented Jan 2, 2018

thanks

@brazilianldsjaguar

This comment has been minimized.

Copy link

@brazilianldsjaguar brazilianldsjaguar commented Aug 30, 2018

Seems that the ECMAScript/JavaScript support is ECMAScript 5 - which means no import statements or anything like that. I was able to get any interaction by doing fully-declared namespaced classes, e.g.:

var processList = com.intellij.execution.process.OSProcessUtil.getProcessList();
@tdegrunt

This comment has been minimized.

Copy link

@tdegrunt tdegrunt commented Sep 27, 2018

This is a very interesting feature, but without examples, it's hard. Anything in Kotlin or JavaScript?

@muthuishere

This comment has been minimized.

Copy link

@muthuishere muthuishere commented Oct 30, 2018

I am getting ex

Tried the Groovy example:

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

But got this error:

MissingMethodException: No signature of method: org.codehaus.groovy.jsr223.GroovyScriptEngineImpl.action() is applicable for argument types: (java.lang.String, java.lang.String, Script1$_run_closure1) values: [TryMe!, alt shift P, Script1$_run_closure1@1e21e437]
Possible solutions: notify(), wait()

I'm presuming this example should add a right-click context menu called TryMe!, which finds usages of the token selected?

I am getting the same exception, Can someone help?

@frankgerhardt

This comment has been minimized.

Copy link

@frankgerhardt frankgerhardt commented Dec 2, 2018

Kotlin examples wanted!

@glockbender

This comment has been minimized.

Copy link

@glockbender glockbender commented Mar 4, 2019

Kotlin examples wanted!

Kotlin example with output

import com.intellij.openapi.ui.Messages.showInfoMessage
var sum: Long = 0L
val arr = "35907 77134 453661 175096 23673 29350"
    .split(" ")
arr.forEach { sum+=it.length }

showInfoMessage((sum.toFloat() / arr.size).toString(), "test")
@mnowotnik

This comment has been minimized.

Copy link

@mnowotnik mnowotnik commented Mar 25, 2019

Getting current project and editor in Kotlin:

val dataContext = DataManager.getInstance().dataContextFromFocusAsync.blockingGet(1000)!!
val project = CommonDataKeys.PROJECT.getData(dataContext)!!
val editor = BaseCodeInsightAction.getInjectedEditor(project, CommonDataKeys.EDITOR.getData(dataContext))!!
@smaudet

This comment has been minimized.

Copy link

@smaudet smaudet commented Jun 4, 2019

IDE.print is no longer functional. It does not display to the run toolwindow and no console appears. Additionally the static method is private and constructing the IDE instance manually yields no results.

Fallback, although not pretty, is to open up File I/O directly and e.g. tail -f <yourfile>

@smaudet

This comment has been minimized.

Copy link

@smaudet smaudet commented Jun 4, 2019

I will also say from what I could tell it is not possible to gain console output from the scripting console into the run dialog, not without either:
a) Discovering the runtime console during run and injecting something into it
b) Collecting and dumping the console output internal to the script
c) Building a new Tool Window and manually outputting console output to it.

@ice1000

This comment has been minimized.

Copy link

@ice1000 ice1000 commented Jul 24, 2019

Kotlin example request

@shawkinaw

This comment has been minimized.

Copy link

@shawkinaw shawkinaw commented Oct 10, 2019

Anybody know if there's a way to run an IDE script from the command line somehow? I'd like to trigger a script I've written from an external script.

@mgroth0

This comment has been minimized.

Copy link

@mgroth0 mgroth0 commented Jun 15, 2020

I'd like to echo @shawkinaw 's question. Also, can we script the embedded terminal? It would be great if I could automatically open a terminal tab, name it, and run a command

@gregsh

This comment has been minimized.

Copy link
Owner Author

@gregsh gregsh commented Jun 16, 2020

Also, can we script the embedded terminal?

An IDE scripting console script, like the following:

com.intellij.openapi.components.ServiceManager.getService(IDE.project,
  com.intellij.sh.run.ShRunner.class).run("ls", "/", "title")

UPD Updated to address @skissane note below.

@gregsh

This comment has been minimized.

Copy link
Owner Author

@gregsh gregsh commented Jun 16, 2020

I've just added a Kotlin *.kts example to show the external bindings access which is tricky at the moment,
and updated IDE Scripting.md with the list of available languages/engines.

@skissane

This comment has been minimized.

Copy link

@skissane skissane commented Jul 13, 2020

@gregsh I thought @shawkinaw's question was, how do I use the IntelliJ command line interface to run an IDE Console script from a shell script? You seem to be answering the opposite question of how I run a shell script from the IDE Console script?

@gregsh

This comment has been minimized.

Copy link
Owner Author

@gregsh gregsh commented Jul 13, 2020

Anybody know if there's a way to run an IDE script from the command line somehow?

No such thing now, but a simple plugin with a com.intellij.appStarter extension shall do the trick.

UPD I'll do that, see https://youtrack.jetbrains.com/issue/IDEA-245847

@sesm

This comment has been minimized.

Copy link

@sesm sesm commented Oct 27, 2020

I've tried .project.clj but I get RuntimeException: Unable to resolve symbol: fileEditors in this context.
I guess this symbol should be added to namespace in the first loop. I checked which symbols are added and the closest I see is fileEditorProviders. Should I use that one to get fileEditors somehow?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.