20

I am currently at the move to write an IntelliJ plugin. I want to be able to store/restore a set of tabs to switch between different tab sessions (comparable to browser plugins like Session Manager or Session Buddy).

Therefore i need basically three types of actions:

  1. Read open tabs (which file and editor is used?)
  2. Store that information persistently as a tab session
  3. Open tabs of selected session and close all others

I looked at the available actions: IdeActions.java - it seems that there is not what i am searching for. But maybe i am looking at the wrong place. Can anyone tell me if what's i am trying to achieve is possible and give me some pointers in the right direction?

Update

I succesfully created the plugin and it's available at Github: http://alp82.github.com/idea-tabsession/

It is available in the official plugin repository: Tab Session.

Update 2

Here is a follow-up question regarding splitted windows: Retrieving and setting split window settings

Community
  • 1
  • 1
Alp
  • 27,423
  • 26
  • 110
  • 190

1 Answers1

24

Update 2017

I discontinue support for this plugin because IDEA already supports that feature. You can save and load contexts easily as shown here: https://github.com/alp82/idea-tabsession#discontinued

Update

The plugin is up and ready and can be downloaded in IDEA -> Settings -> Plugins. Source code is available at: https://github.com/alp82/idea-tabsession

Short Answer

To read which tabs are open right now, use the EditorFactory and FileDocumentManager Singletons:

    Editor[] editors = EditorFactory.getInstance().getAllEditors();
    FileDocumentManager fileDocManager = FileDocumentManager.getInstance();
    for(Editor editor : editors) {
        VirtualFile vf = fileDocManager.getFile(editor.getDocument());
        String path = vf.getCanonicalPath();
        System.out.println("path = " + path);
    }

To open tabs use the FileEditorManager singleton (files being a String Array of canonical paths):

    FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
    for(String path : files) {
        System.out.println("path = " + path);
        VirtualFile vf = LocalFileSystem.getInstance().findFileByPath(path);
        fileEditorManager.openFile(vf, true, true);
    }

Long Answer

Prerequisites

  1. Activate the Plugin Development, Groovy and UI Designer plugins
  2. New Project -> IntelliJ IDEA Plugin
  3. Checkout IDEA Community Edition sources to any folder:

    git clone git://git.jetbrains.org/idea/community.git idea
    
  4. Configure IDEA SDK and create plugin

Plugin Structure

After you have created your plugin you need to edit your plugin.xml located in the META-INF folder. Modify the id, name and description.

We need a configuration file for persistant storage. Create a mystorage.xml file in your src folder. It's now time to create the needed files:

SessionComponent.java (create it with the Add Project Component wizard to automatically create the needed xml settings):

@State(
    name = "SessionComponent",
    storages = {
        @Storage(id = "default", file = StoragePathMacros.PROJECT_FILE),
        @Storage(id = "dir", file = StoragePathMacros.PROJECT_CONFIG_DIR + "/mystorage.xml", scheme = StorageScheme.DIRECTORY_BASED)
    }
)
public class SessionComponent implements ProjectComponent, PersistentStateComponent<SessionState> {

    Project project;
    SessionState sessionState;

    public SessionComponent(Project project) {
        this.project = project;
        sessionState = new SessionState();
    }

    public void initComponent() {
        // TODO: insert component initialization logic here
    }

    @Override
    public void loadState(SessionState sessionState) {
        System.out.println("load sessionState = " + sessionState);
        this.sessionState = sessionState;
    }

    public void projectOpened() {
        // called when project is opened
    }

    public void projectClosed() {
        // called when project is being closed
    }

    @Nullable
    @Override
    public SessionState getState() {
        System.out.println("save sessionState = " + sessionState);
        return sessionState;
    }

    public void disposeComponent() {
        // TODO: insert component disposal logic here
    }

    @NotNull
    public String getComponentName() {
        return "SessionComponent";
    }

    public int saveCurrentTabs() {
        Editor[] editors = getOpenEditors();
        FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
        VirtualFile[] selectedFiles = fileEditorManager.getSelectedFiles();

        FileDocumentManager fileDocManager = FileDocumentManager.getInstance();
        sessionState.files = new String[editors.length];
        int i = 0;
        for(Editor editor : editors) {
            VirtualFile vf = fileDocManager.getFile(editor.getDocument());
            String path = vf.getCanonicalPath();
            System.out.println("path = " + path);
            if(path.equals(selectedFiles[0].getCanonicalPath())) {
                sessionState.focusedFile = path;
            }
            sessionState.files[i] = path;
            i++;
        }

        return editors.length;
    }

    public int loadSession() {
        closeCurrentTabs();
        FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
        for(String path : sessionState.files) {
            System.out.println("path = " + path);
            VirtualFile vf = LocalFileSystem.getInstance().findFileByPath(path);
            fileEditorManager.openFile(vf, true, true);
        }

        return sessionState.files.length;
    }

    public void closeCurrentTabs() {
        Editor[] editors = getOpenEditors();
        FileEditorManager fileEditorManager = FileEditorManager.getInstance(project);
        FileDocumentManager fileDocManager = FileDocumentManager.getInstance();
        for(Editor editor : editors) {
            System.out.println("editor = " + editor);
            VirtualFile vf = fileDocManager.getFile(editor.getDocument());
            fileEditorManager.closeFile(vf);
        }
    }

    public void showMessage(String htmlText) {
        StatusBar statusBar = WindowManager.getInstance().getStatusBar(project);
        JBPopupFactory.getInstance()
            .createHtmlTextBalloonBuilder(htmlText, MessageType.INFO, null)
            .setFadeoutTime(7500)
            .createBalloon()
            .show(RelativePoint.getCenterOf(statusBar.getComponent()), Balloon.Position.atRight);
    }

    private Editor[] getOpenEditors() {
        return EditorFactory.getInstance().getAllEditors();
    }

}

We also need the storage class:

public class SessionState {
    public String[] files = new String[0];
    public String focusedFile = "";

    public String toString() {
        String result = "";
        for (String file : files) {
            result += file + ", ";
        }
        result += "selected: " + focusedFile;
        return result;
    }
}

The component class should have an entry in your plugin.xml like this one:

<project-components>
  <component>
    <implementation-class>my.package.SessionComponent</implementation-class>
  </component>
</project-components>

The component class offers all needed functionality, but is never be used. Therefore, we need actions to perform loading and saving:

Save.java:

public class Save extends AnAction {

    public Save() {
        super();
    }

    public void actionPerformed(AnActionEvent event) {
        Project project = event.getData(PlatformDataKeys.PROJECT);
        SessionComponent sessionComponent = project.getComponent(SessionComponent.class);

        int tabCount = sessionComponent.saveCurrentTabs();
        String htmlText = "Saved " + String.valueOf(tabCount) + " tabs";
        sessionComponent.showMessage(htmlText);
    }

}

Load.java:

public class Load extends AnAction {

    public Load() {
        super();
    }

    public void actionPerformed(AnActionEvent event) {
        Project project = event.getData(PlatformDataKeys.PROJECT);
        SessionComponent sessionComponent = project.getComponent(SessionComponent.class);

        int tabCount = sessionComponent.loadSession();
        String htmlText = "Loaded " + String.valueOf(tabCount) + " tabs";
        sessionComponent.showMessage(htmlText);
    }

}

Aaand... Action!

Last thing we need is the user interface to select those actions. Simply put this in your plugin.xml:

  <actions>
    <!-- Add your actions here -->
      <group id="MyPlugin.SampleMenu" text="_Sample Menu" description="Sample menu">
          <add-to-group group-id="MainMenu" anchor="last"  />
          <action id="MyPlugin.Save" class="my.package.Save" text="_Save" description="A test menu item" />
          <action id="MyPlugin.Load" class="my.package.Load" text="_Load" description="A test menu item" />
      </group>
  </actions>

Plugin Deployment

The basic functionality is ready. I will add support for multiple sessions and some other neat stuff before deploying this plugin and releasing it to the open-source community. Link will be posted here, when it's online.

Alp
  • 27,423
  • 26
  • 110
  • 190
  • Doesn't work properly for Intellij IDEA 11.1.5 (x64) – dnim Feb 27 '14 at 12:40
  • Please fill in an [issue on Github](https://github.com/alp82/idea-tabsession/issues) with a problem description. – Alp Feb 27 '14 at 13:33
  • @Alp This plugin can't handle to switch between groups poperly. It will close all others down, and use a current group of tabs instead of opening a new splitter or window. It would be nice if several groups can be opened and tabs be made uncloseable for each group and also so they can live in various splitters and popped out windows. Right now, it just replaces tabs. It even closes all splitters when it does so. – mmm Sep 30 '14 at 16:44
  • I can't get the plugin to compile so I can't add this functionality myself. No idea how to get started. Could you turn the entire module to a maven or include all libraries and provide a working intellij module. Would make it easy to just add this kind of functionality – mmm Sep 30 '14 at 16:51
  • Please fill an [issue on Guthub](https://github.com/alp82/idea-tabsession/issues). – Alp Sep 30 '14 at 19:22
  • I have IntelliJ IDEA Ultimate and the Tasks & Contexts menu was not enabled. Perhaps I disabled it during installation. I had to go to the Install Plugins dialog and enable this feature for the menu item to appear. – Code-Apprentice Sep 05 '17 at 16:00