注册 登录  
 加关注
   显示下一条  |  关闭
温馨提示!由于新浪微博认证机制调整,您的新浪微博帐号绑定已过期,请重新绑定!立即重新绑定新浪微博》  |  关闭

断尘居

温柔的男人像海洋。

 
 
 
 
 

日志

 
 

IntelliJ IDEA Architectural Overview  

2015-11-27 16:39:56|  分类: IntelliJ IDEA |  标签: |举报 |字号 订阅

  下载LOFTER 我的照片书  |

This topic describes the architecture of the IntelliJ Platform from a plugin developer’s point of view. It is organized in a task-based manner to answer specific questions like “what can I do with this object?”, “how do I get to this object?” and so on.

Before proceeding please make sure you’re familiar with the basic concepts of IntelliJ Platform plugin development. If not, consider starting with the live demo and tutorials at www.jetbrains.com/idea/plugins/ and then returning to this document.

The following subjects are covered:

General Threading Rules

In general, data structures in the IntelliJ Platform are covered by a single reader/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 by usingApplicationManager.getApplication().runReadAction().

Writing data is only allowed from the UI thread, and write operations always need to be wrapped in a write action with ApplicationManager.getApplication().runWriteAction().

To pass control from a background thread to the event dispatch thread, instead of the standardSwingUtilities.invokeLater(), plugins should use ApplicationManager.getApplication().invokeLater(). The latter API allows specifying the modality state for the call, i.e. 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. 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 file system (VFS). Most commonly, a virtual file is a file in your local file system. However, IntelliJ IDEA supports multiple pluggable file system implementations, so virtual files can also represent classes in a JAR file, old revisions of files loaded from a version control 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 on higher system levels.

How do I get a virtual file?

  • From an action: e.getData(PlatformDataKeys.VIRTUAL_FILE). If you are interested in multiple selection, you can also use e.getData(PlatformDataKeys.VIRTUAL_FILE_ARRAY).
  • From a path in the local file system: LocalFileSystem.getInstance().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 it?

Typical file operations are available, such as traverse the file system, get file contents, rename, move, or delete. Recursive iteration should be performed using VfsUtilCore.iterateChildrenRecursively to prevent endless loops caused by recursive symlinks.

Where does it come from?

The VFS is built incrementally, by scanning the file system up and down starting from the project root. New files appearing in the file system are detected by VFS refreshes. A refresh operation can be initiated programmatically using (VirtualFileManager.getInstance().refresh() or VirtualFile.refresh()). VFS refreshes are also triggered whenever file system watchers receive file system change notifications (available on the Windows and Mac operating systems).

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

How long does a virtual file persist?

A particular file on disk is represented by equal VirtualFile instances for the entire lifetime of the IDEA process. There may be several instances corresponding to the same file, and they can be garbage-collected. The file is aUserDataHolder, and the user data is shared between those equal instances. If a file is deleted, its corresponding VirtualFile instance becomes invalid (the isValid() method returns false and operations cause exceptions).

How do I create a virtual file?

Usually you don’t. As a rule, files are created either through the PSI API or through the regular java.io.File API.

If you do need to create a file through VFS, you can use the VirtualFile.createChildData() method to create aVirtualFile instance and the VirtualFile.setBinaryContent() method to write some data to the file.

How do I get notified when VFS changes?

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

How do I extend VFS?

To provide an alternative file system implementation (for example, an FTP file system), implement theVirtualFileSystem class (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 are developing a version control system integration that needs custom rename/move handling), implement the LocalFileOperationsHandlerinterface and register it through theLocalFileSystem.registerAuxiliaryFileOperationsHandler method.

What are the rules for working with VFS?

See IntelliJ Platform Virtual File System for a detailed description of the VFS architecture and usage guidelines.

Documents

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. The IntelliJ Platform handles encoding and line break conversions when loading and saving documents transparently.

How do I get a document?

  • From an action: e.getData(PlatformDataKeys.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() orPsiDocumentManager.getInstance().getCachedDocument()

What can I do with a Document?

You may perform any operations that access or modify the file contents on “plain text” level (as a sequence of characters, not as a tree of Java elements).

Where does a Document 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 a Document persist?

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 a Document?

If you need to create a new file on disk, you don’t create a Document: you create a PSI file and then get itsDocument. If you need to create a Document instance which isn’t bound to anything, you can useEditorFactory.createDocument.

How do I get notified when Documents change?

  • Document.addDocumentListener allows you to receive notifications about changes in a particular Documentinstance.
  • 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 Documents?

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 callReadonlyStatusHandler.getInstance(project).ensureFilesWritable() to check out the file if necessary.

All text strings passed to Document modification methods (setTextinsertStringreplaceString) must use only \n as line separators.

PSI Files

A PSI (Program Structure Interface) file is the root of a structure representing the contents of a file as a hierarchy of elements in a particular programming language.

The PsiFile class is the common base class for all PSI files, while files in a specific language are usually represented by its subclasses. For example, the PsiJavaFile class represents a Java file, and the XmlFile class represents an XML file.

Unlike VirtualFile and Document, which have application scope (even if multiple projects are open, each file is represented by the same VirtualFile instance), PSI has project scope (the same file is represented by multiplePsiFile instances if the file belongs to multiple projects open at the same time).

How do I get a PSI file?

  • From an action: e.getData(LangDataKeys.PSI_FILE).
  • From a VirtualFile: PsiManager.getInstance(project).findFile()
  • From a Document: PsiDocumentManager.getInstance(project).getPsiFile()
  • From an element inside the file: psiElement.getContainingFile()
  • To find files with a specific name anywhere in the project, use FilenameIndex.getFilesByName(project, name, scope)

What can I do with a PSI file?

Most interesting modification operations are performed on the level of individual PSI elements, not files as a whole.

To iterate over the elements in a file, use psiFile.accept(new PsiRecursiveElementWalkingVisitor()...);

Where does it a PSI file come from?

As PSI is language-dependent, PSI files are created through the Language object, by using theLanguageParserDefinitions.INSTANCE.forLanguage(language).createFile(fileViewProvider) method.

Like documents, PSI files are created on demand when the PSI is accessed for a particular file.

How long do PSI files persist?

Like documents, PSI files are weakly referenced from the corresponding VirtualFile instances and can be garbage-collected if not referenced by anyone.

How do I create a PSI file?

The PsiFileFactory.getInstance(project).createFileFromText() method creates an in-memory PSI file with the specified contents.

To save the PSI file to disk, use the PsiDirectory.add() method.

How do I get notified when PSI files change?

PsiManager.getInstance(project).addPsiTreeChangeListener() allows you to receive notifications about all changes to the PSI tree of a project.

How do I extend PSI?

PSI can be extended to support additional languages through custom language plugins. For more details on developing custom language plugins, see the Custom Language Support reference guide.

What are the rules for working with PSI?

Any changes done to the content of PSI files are reflected in documents, so all rules for working with documents (read/write actions, commands, read-only status handling) are in effect.


File View Providers

A file view provider (see the FileViewProvider class) was introduced in IntelliJ IDEA 6.0. Its main purpose is to manage access to multiple PSI trees within a single file.

For example, a JSPX page has a separate PSI tree for the Java code in it (PsiJavaFile), a separate tree for the XML code (XmlFile), and a separate tree for JSP as a whole JspFile).

Each of the PSI trees covers the entire contents of the file, and contains special “outer language elements” in the places where contents in a different language can be found.

FileViewProvider instance corresponds to a single VirtualFile, a single Document, and can be used to retrieve multiple PsiFile instances.

How do I get an FVP?

  • From a VirtualFile: PsiManager.getInstance(project).findViewProvider()
  • From a PSI file: psiFile.getViewProvider()

What can I do with an FVP?

  • To get the list of all languages for which PSI trees exist in a file: fileViewProvider.getLanguages()
  • To get the PSI tree for a particular language: fileViewProvider.getPsi(language), where the languageparameter can take values of the Language type defined in StdLanguages class. For example, to get the PSI tree for XML, use fileViewProvider.getPsi(StdLanguages.XML).
  • To find an element of a particular language at the specified offset in the file:fileViewProvider.findElementAt(offset,language)

How do I extend the FileViewProvider?

To create a file type that has multiple interspersing trees for different languages, your plugin must contain an extension to the fileType.fileViewProviderFactory extension point available in the IntelliJ Platform core.

This extension point is declared using the FileTypeExtensionPoint bean class.

To access this extension point, create a Java class that implements the FileViewProviderFactory interface, and in this class, override the createFileViewProvider method.

To declare the extension to the fileType.fileViewProviderFactory extension point, to the <extensions> section of the plugin.xml file, add the following syntax:

<extensions>
  <fileType.fileViewProviderFactory filetype="%file_type%" implementationClass="%class_name%" />
</extensions>

Where %file_type% refers to the type of the file being created (for example, “JFS”), and the %class_name% refers to the name of your Java class that implements the FileViewProviderFactory interface.


PSI Elements

A PSI (Program Structure Interface) file represents a hierarchy of PSI elements (so-called PSI trees). A single PSI file may include several PSI trees in a particular programming language. A PSI element, in its turn, can have child PSI elements.

PSI elements and operations on the level of individual PSI elements are used to explore the internal structure of source code as it is interpreted by IntelliJ IDEA. For example, you can use PSI elements to perform code analysis, such as code inspections or intention actions.

The PsiElement class is the common base class for PSI elements.

How do I get a PSI element?

  • From an action: e.getData(LangDataKeys.PSI_ELEMENT). Note: if an editor is currently open and the element under caret is a reference, this will return the result of resolving the reference. This may or may not be what you need.
  • From a file by offset: PsiFile.findElementAt(). Note: this returns the lowest level element at the specified offset, which is normally a lexer token. Most likely you should use PsiTreeUtil.getParentOfType() to find the element you really need.
  • By iterating through a PSI file: using a PsiRecursiveElementWalkingVisitor.
  • By resolving a reference: PsiReference.resolve()

What can I do with PSI elements?

See PSI Cook Book



  评论这张
 
阅读(611)| 评论(0)
推荐 转载

历史上的今天

在LOFTER的更多文章

评论

<#--最新日志,群博日志--> <#--推荐日志--> <#--引用记录--> <#--博主推荐--> <#--随机阅读--> <#--首页推荐--> <#--历史上的今天--> <#--被推荐日志--> <#--上一篇,下一篇--> <#-- 热度 --> <#-- 网易新闻广告 --> <#--右边模块结构--> <#--评论模块结构--> <#--引用模块结构--> <#--博主发起的投票-->
 
 
 
 
 
 
 
 
 
 
 
 
 
 

页脚

网易公司版权所有 ©1997-2017