Child pages
  • IntelliJ IDEA Architectural Overview
Skip to end of metadata
Go to start of metadata

You are viewing an old version of this page. View the current version.

Compare with Current View Page History

« Previous Version 11 Next »

This document is a work in progress. Feel free to leave comments if you feel that some information should be added or clarified.

The goal of this document is to describe the architecture of IntelliJ IDEA 6.0 from a plugin developer's point of view. It will be organized in a task-based manner: rather than listing all the things that you can do with each object and describing how they are all implemented, it will try to answer questions "what can I do with this object", "how do I get to this object" and so on.

This document assumes that the reader is familiar with the basic concepts of IntelliJ IDEA plugin development. If you don't know anything at all about plugin development, you should start with the live demo and tutorials at, and then return to this document.

While the document is focused on version 6.0, most of the core architecture didn't change between versions 5 and 6. Some of the places where version 6 is different will be highlighted.

General Threading Rules

In general, the data structures of IntelliJ IDEA are covered by a single "multiple readers / single writer" lock. Reading data is allowed from any thread. Reading data from the UI thread does not require any special effort, however, read operations performed from any other thread need to be wrapped in a read action (ApplicationManager.getApplication().runReadAction()). Writing the data is only allowed from the UI thread, and write operations always need to be wrapped in a write action (ApplicationManager.getApplication().runWriteAction()). Note that the thread spawned to perform operations with a modal progress indicator (ProgressManager.getInstance().runProcessWithProgressSynchronously) is also allowed to perform write operations.

To pass control from a background thread to the event dispatch thread, instead of the standard SwingUtilities.invokeLater(), plugins should use ApplicationManager.getApplication().invokeLater(). The latter API allows to specify the modality state for the call - the stack of modal dialogs under which the call is allowed to execute. Passing ModalityState.NON_MODAL means that the operation will be executed after all modal dialogs are closed, and passing ModalityState.stateForComponent() means that the operation may be executed while the specified component (part of a dialog) is still visible.

Virtual Files

A virtual file (com.intellij.openapi.vfs.VirtualFile) is IntelliJ IDEA's representation of a file in a filesystem (VFS). The most common case of a virtual file is a file on your local filesystem. However, IDEA supports multiple pluggable filesystem implementations, so virtual files can also represent classes in a JAR file, old revisions of files loaded from the CVS repository and so on.

The VFS level deals only with binary content. You can get or set the contents of a VirtualFile as a stream of bytes, but concepts like encodings and line separators are handled by higher levels of the system.

How do I get one?

  • From an action: (VirtualFile) dataContext.getData(DataConstants.VIRTUAL_FILE). If you are interested in multiple selection, you can also query for (VirtualFile[]) dataContext.getData(DataConstants.VIRTUAL_FILE_ARRAY).
  • From a path in the local file system: LocalFileSystem.findFileByIoFile()
  • From a PSI file: psiFile.getVirtualFile() (may return null if the PSI file exists only in memory)
  • From a document: FileDocumentManager.getInstance().getFile()

What can I do with one?

Traverse the file system, get file contents, rename, move, delete - typical file operations.

Where does it come from?

The VFS is built incrementally, by walking the filesystem up and down starting from the project root. New files appearing in the file system are detected by VFS refreshes. A refresh can be initiated programmatically (VirtualFileManager.refresh() or VirtualFile.refresh()). VFS refreshes are also caused by file system change notifications received by file system watchers (on Windows and Mac).

As a plugin developer, you may need to invoke a VFS refresh if you need to access through IDEA's APIs a file which has just been created by an external tool.

How long does it live?

A particular file on disk is represented by the same VirtualFile instance for the entire lifetime of the IDEA process. If a file is deleted, its corresponding VirtualFile instance becomes invalid (isValid() returns false and operations cause exceptions).

How do I create one?

Usually you don't: files are created either through the PSI API or through the regular API. If you do need to create a file through VFS, you can use VirtualFile.createChildData() to create a VirtualFile instance and VirtualFile.setBinaryContent() to write some data to it.

How do I get notified when it changes?

VirtualFileManager.addVirtualFileListener() allows you to receive notifications about all changes happening in the VFS.

How do I extend it?

To provide an alternative filesystem implementation (for example, an FTP filesystem), implement the VirtualFileSystem interface (most likely you'll also need to implement VirtualFile), and register your implementation as an application component. To hook into operations performed in the local file system (for example, if you're developing a version control system integration which needs custom rename/move handling), implement LocalFileOperationsHandler and register it through LocalFileSystem.registerAuxiliaryFileOperationsHandler.

What are the rules for working with it?

The general read action/write action rules are in effect. Note that any operation that can cause a synchronous refresh of the VFS (for example, LocalFileSystem.refreshAndFindFileByIoFile()) is a write action and can only performed in the UI thread. If you need to refresh VFS from a background thread, use asynchronous refresh operations with a postRunnable specified.


A document is an editable sequence of Unicode characters, which typically corresponds to the text contents of a virtual file. Line breaks in a document are always normalized to \n. IntelliJ IDEA handles encoding and line break conversions when loading and saving documents transparently.

How do I get one?

  • From an action: ((Editor) dataContext.getData(DataConstants.EDITOR)).getDocument()
  • From a virtual file: FileDocumentManager.getDocument(). This call forces the document content to be loaded from disk if it wasn't loaded previously; if you're only interested in open documents or documents which may have been modified, use FileDocumentManager.getCachedDocument() instead.
  • From a PSI file: PsiDocumentManager.getInstance().getDocument() or PsiDocumentManager.getInstance().getCachedDocument()

What can I do with one?

Any operations which access or modify the file contents on "plain text" level (as a sequence of characters, not as a tree of Java elements).

Where does it come from?

Document instances are created when some operation needs to access the text contents of a file (in particular, this is needed to build the PSI for a file). Also, document instances not linked to any virtual files can be created temporarily, for example, to represent the contents of a text editor field in a dialog.

How long does it live?

Document instances are weakly referenced from VirtualFile instances. Thus, an unmodified Document instance can be garbage-collected if it isn't referenced by anyone, and a new instance will be created if the document contents is accessed again later. Storing Document references in long-term data structures of your plugin will cause memory leaks.

How do I create one?

If you need to create a new file on disk, you don't create a document: you create a PSI file and then get its document. If you need to create a document instance which isn't bound to anything, you can use EditorFactory.createDocument.

How do I get notified when it changes?

  • Document.addDocumentListener allows you to receive notifications about changes in a particular Document instance.
  • EditorFactory.getEventMulticaster().addDocumentListener allows you to receive notifications about changes in all open documents.
  • FileDocumentManager.addFileDocumentManagerListener allows you to receive notifications when any document is saved or reloaded from disk.

What are the rules of working with it?

The general read/write action rules are in effect. In addition to that, any operations which modify the contents of the document must be wrapped in a command (CommandProcessor.getInstance().executeCommand()). executeCommand() calls can be nested, and the outermost executeCommand call is added to the undo stack. If multiple documents are modified within a command, undoing this command will by default show a confirmation dialog to the user.

If the file corresponding to a document is read-only (for example, not checked out from the version control system), document modifications will fail. Thus, before modifying the document, it is necessary to call ReadonlyStatusHandler.getInstance(project).ensureFilesWritable() to check out the file if necessary.

All text strings passed to document modification methods (setText, insertString, replaceString) must use only \n as line separators.

PSI Files

A PSI file is the root of a structure representing the contents of a file as a hierarchy of elements in a particular programming language. com.intellij.psi.PsiFile is the common base class for all PSI files, and files in a specific language are usually represented by its subclasses. For example, com.intellij.psi.PsiJavaFile represents a Java file, and com.intellij.psi.xml.XmlFile represents an XML file.


to be continued

  • No labels