Terminal tabs: drag'n'drop to editor and back implemented.

This commit is contained in:
Dmitry Trofimov
2013-11-10 18:03:51 +01:00
parent 6ac9ae9c7b
commit 4b2204574d
9 changed files with 623 additions and 4 deletions
@@ -0,0 +1,21 @@
<?xml version="1.0" encoding="UTF-8"?>
<module type="JAVA_MODULE" version="4">
<component name="NewModuleRootManager" inherit-compiler-output="true">
<exclude-output />
<content url="file://$MODULE_DIR$">
<sourceFolder url="file://$MODULE_DIR$/src" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/../sandbox/resources" isTestSource="false" />
<sourceFolder url="file://$MODULE_DIR$/resources" isTestSource="false" />
</content>
<orderEntry type="inheritedJdk" />
<orderEntry type="sourceFolder" forTests="false" />
<orderEntry type="module" module-name="git4idea" exported="" />
<orderEntry type="module" module-name="remote-servers-impl" exported="" />
<orderEntry type="module" module-name="core-api" exported="" />
<orderEntry type="module" module-name="remote-servers-api" />
<orderEntry type="module" module-name="vcs-api" />
<orderEntry type="module" module-name="lang-api" />
<orderEntry type="module" module-name="lang-impl" />
</component>
</module>
@@ -12,6 +12,8 @@
factoryClass="org.jetbrains.plugins.terminal.TerminalToolWindowFactory" secondary="false"/>
<projectConfigurable instance="org.jetbrains.plugins.terminal.TerminalOptionsConfigurable"/>
<fileEditorProvider implementation="org.jetbrains.plugins.terminal.vfs.TerminalSessionEditorProvider"/>
</extensions>
<project-components>
@@ -5,11 +5,17 @@ import com.intellij.openapi.Disposable;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.AnActionEvent;
import com.intellij.openapi.actionSystem.Presentation;
import com.intellij.openapi.fileEditor.impl.EditorTabbedContainer;
import com.intellij.openapi.fileEditor.impl.FileEditorManagerImpl;
import com.intellij.openapi.project.DumbAwareAction;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.wm.IdeFocusManager;
import com.intellij.ui.SimpleColoredComponent;
import com.intellij.ui.components.JBTextField;
import com.intellij.ui.docking.DockContainer;
import com.intellij.ui.docking.DockManager;
import com.intellij.ui.docking.DragSession;
import com.intellij.ui.tabs.TabInfo;
import com.intellij.ui.tabs.TabsListener;
import com.intellij.ui.tabs.impl.JBEditorTabs;
@@ -19,6 +25,7 @@ import com.jediterm.terminal.ui.*;
import com.jediterm.terminal.ui.settings.SettingsProvider;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.terminal.vfs.TerminalSessionVirtualFileImpl;
import javax.swing.*;
import javax.swing.event.ChangeEvent;
@@ -86,6 +93,8 @@ public class JBTabbedTerminalWidget extends TabbedTerminalWidget {
public class JBTerminalTabs implements TerminalTabs {
private final JBEditorTabs myTabs;
private TabInfo.DragOutDelegate myDragDelegate = new MyDragOutDelegate();
private final CopyOnWriteArraySet<ChangeListener> myListeners = new CopyOnWriteArraySet<ChangeListener>();
public JBTerminalTabs(@NotNull Project project, @NotNull Disposable parent) {
@@ -170,7 +179,7 @@ public class JBTabbedTerminalWidget extends TabbedTerminalWidget {
}
@Override
public Component getComponent() {
public JComponent getComponent() {
return myTabs.getComponent();
}
@@ -181,7 +190,12 @@ public class JBTabbedTerminalWidget extends TabbedTerminalWidget {
@Override
public void addTab(String name, JediTermWidget terminal) {
myTabs.addTab(new TabInfo(terminal).setText(name));
myTabs.addTab(createTabInfo(name, terminal));
}
private TabInfo createTabInfo(String name, JediTermWidget terminal) {
return new TabInfo(terminal).setText(name).setDragOutDelegate(myDragDelegate)
.setObject(new TerminalSessionVirtualFileImpl(name, terminal));
}
public String getTitleAt(int i) {
@@ -289,7 +303,7 @@ public class JBTabbedTerminalWidget extends TabbedTerminalWidget {
@Override
public void setComponent(Component c) {
myTabs.setTabDraggingEnabled(!(c instanceof JBTextField));
setPlaceholderContent(true, (JComponent)c);
}
@@ -300,5 +314,69 @@ public class JBTabbedTerminalWidget extends TabbedTerminalWidget {
});
}
}
class MyDragOutDelegate implements TabInfo.DragOutDelegate {
private TerminalSessionVirtualFileImpl myFile;
private DragSession mySession;
@Override
public void dragOutStarted(MouseEvent mouseEvent, TabInfo info) {
final TabInfo previousSelection = info.getPreviousSelection();
final Image img = JBTabsImpl.getComponentImage(info);
info.setHidden(true);
if (previousSelection != null) {
myTabs.select(previousSelection, true);
}
myFile = (TerminalSessionVirtualFileImpl)info.getObject();
Presentation presentation = new Presentation(info.getText());
presentation.setIcon(info.getIcon());
mySession = getDockManager()
.createDragSession(mouseEvent, new EditorTabbedContainer.DockableEditor(myProject, img, myFile, presentation,
info.getComponent().getPreferredSize(), false));
}
private DockManager getDockManager() {
return DockManager.getInstance(myProject);
}
@Override
public void processDragOut(MouseEvent event, TabInfo source) {
mySession.process(event);
}
@Override
public void dragOutFinished(MouseEvent event, TabInfo source) {
boolean copy =
com.intellij.util.ui.UIUtil.isControlKeyDown(event) || mySession.getResponse(event) == DockContainer.ContentResponse.ACCEPT_COPY;
if (!copy) {
myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, Boolean.TRUE);
// FileEditorManagerEx.getInstanceEx(myProject).closeFile(myFile, myWindow); TODO
}
else {
source.setHidden(false);
}
mySession.process(event);
if (!copy) {
myFile.putUserData(FileEditorManagerImpl.CLOSING_TO_REOPEN, null);
}
myFile = null;
mySession = null;
}
@Override
public void dragOutCancelled(TabInfo source) {
source.setHidden(false);
if (mySession != null) {
mySession.cancel();
}
myFile = null;
mySession = null;
}
}
}
}
@@ -12,8 +12,13 @@ import com.intellij.openapi.wm.ToolWindow;
import com.intellij.openapi.wm.ToolWindowManager;
import com.intellij.openapi.wm.ex.ToolWindowManagerEx;
import com.intellij.openapi.wm.ex.ToolWindowManagerListener;
import com.intellij.ui.awt.RelativePoint;
import com.intellij.ui.awt.RelativeRectangle;
import com.intellij.ui.content.Content;
import com.intellij.ui.content.ContentFactory;
import com.intellij.ui.docking.DockContainer;
import com.intellij.ui.docking.DockManager;
import com.intellij.ui.docking.DockableContent;
import com.intellij.util.ui.UIUtil;
import com.jediterm.terminal.ui.JediTermWidget;
import com.jediterm.terminal.ui.TabbedTerminalWidget;
@@ -21,8 +26,10 @@ import com.jediterm.terminal.ui.TerminalWidget;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.plugins.terminal.vfs.TerminalSessionVirtualFileImpl;
import javax.swing.*;
import java.awt.*;
import java.awt.event.FocusEvent;
import java.awt.event.FocusListener;
@@ -35,6 +42,8 @@ public class TerminalView {
private final Project myProject;
private TerminalDockContainer myDockContainer;
public TerminalView(Project project) {
myProject = project;
}
@@ -81,6 +90,12 @@ public class TerminalView {
}
});
}
if (myDockContainer == null) {
myDockContainer = new TerminalDockContainer(toolWindow);
DockManager.getInstance(myProject).register(myDockContainer);
}
}
private Content createTerminalInContentPanel(@Nullable LocalTerminalDirectRunner terminalRunner,
@@ -118,7 +133,6 @@ public class TerminalView {
toolbar.setTargetComponent(panel);
panel.setToolbar(toolbar.getComponent());
content.setPreferredFocusableComponent(myTerminalWidget.getComponent());
@@ -232,4 +246,91 @@ public class TerminalView {
hideIfNoActiveSessions(myToolWindow, myTerminal);
}
}
/**
* @author traff
*/
public class TerminalDockContainer implements DockContainer {
private ToolWindow myTerminalToolWindow;
public TerminalDockContainer(ToolWindow toolWindow) {
myTerminalToolWindow = toolWindow;
}
@Override
public RelativeRectangle getAcceptArea() {
return new RelativeRectangle(myTerminalToolWindow.getComponent());
}
@NotNull
@Override
public ContentResponse getContentResponse(@NotNull DockableContent content, RelativePoint point) {
return ContentResponse.ACCEPT_MOVE;
}
@Override
public JComponent getContainerComponent() {
return myTerminalToolWindow.getComponent();
}
@Override
public void add(@NotNull DockableContent content, RelativePoint dropTarget) {
if (content.getKey() instanceof TerminalSessionVirtualFileImpl) {
TerminalSessionVirtualFileImpl terminalFile = (TerminalSessionVirtualFileImpl)content.getKey();
myTerminalWidget.addTab(terminalFile.getName(), terminalFile.getTerminal());
}
}
@Override
public void closeAll() {
}
@Override
public void addListener(Listener listener, Disposable parent) {
}
@Override
public boolean isEmpty() {
return false;
}
@Nullable
@Override
public Image startDropOver(@NotNull DockableContent content, RelativePoint point) {
return null;
}
@Nullable
@Override
public Image processDropOver(@NotNull DockableContent content, RelativePoint point) {
return null;
}
@Override
public void resetDropOver(@NotNull DockableContent content) {
}
@Override
public boolean isDisposeWhenEmpty() {
return false;
}
@Override
public void showNotify() {
}
@Override
public void hideNotify() {
}
@Override
public void dispose() {
}
}
}
@@ -0,0 +1,127 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.terminal.vfs;
import com.intellij.codeHighlighting.BackgroundEditorHighlighter;
import com.intellij.ide.structureView.StructureViewBuilder;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorLocation;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.fileEditor.FileEditorStateLevel;
import com.intellij.openapi.util.UserDataHolderBase;
import com.jediterm.terminal.ui.JediTermWidget;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import javax.swing.*;
import java.beans.PropertyChangeListener;
/**
* @author traff
*/
public class TerminalSessionEditor extends UserDataHolderBase implements FileEditor {
private final JediTermWidget myTerminal;
private final String myName;
public TerminalSessionEditor(@NotNull TerminalSessionVirtualFileImpl terminalFile) {
myTerminal = terminalFile.getTerminal();
myName = terminalFile.getName();
}
@NotNull
@Override
public JComponent getComponent() {
return myTerminal;
}
@Nullable
@Override
public JComponent getPreferredFocusedComponent() {
return myTerminal;
}
@NotNull
@Override
public String getName() {
return myName;
}
@NotNull
@Override
public FileEditorState getState(@NotNull FileEditorStateLevel level) {
return FileEditorState.INSTANCE;
}
@Override
public void setState(@NotNull FileEditorState state) {
}
@Override
public boolean isModified() {
return false;
}
@Override
public boolean isValid() {
return true;
}
@Override
public void selectNotify() {
}
@Override
public void deselectNotify() {
}
@Override
public void addPropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Override
public void removePropertyChangeListener(@NotNull PropertyChangeListener listener) {
}
@Nullable
@Override
public BackgroundEditorHighlighter getBackgroundHighlighter() {
return null;
}
@Nullable
@Override
public FileEditorLocation getCurrentLocation() {
return null;
}
@Nullable
@Override
public StructureViewBuilder getStructureViewBuilder() {
return null;
}
@Override
public void dispose() {
myTerminal.close();
}
}
@@ -0,0 +1,71 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.terminal.vfs;
import com.intellij.openapi.fileEditor.FileEditor;
import com.intellij.openapi.fileEditor.FileEditorPolicy;
import com.intellij.openapi.fileEditor.FileEditorProvider;
import com.intellij.openapi.fileEditor.FileEditorState;
import com.intellij.openapi.project.DumbAware;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.util.Disposer;
import com.intellij.openapi.vfs.VirtualFile;
import org.jdom.Element;
import org.jetbrains.annotations.NotNull;
/**
* @author traff
*/
public class TerminalSessionEditorProvider implements FileEditorProvider, DumbAware {
@Override
public boolean accept(@NotNull Project project, @NotNull VirtualFile file) {
return file instanceof TerminalSessionVirtualFileImpl;
}
@NotNull
@Override
public FileEditor createEditor(@NotNull Project project, @NotNull VirtualFile file) {
return new TerminalSessionEditor((TerminalSessionVirtualFileImpl)file);
}
@Override
public void disposeEditor(@NotNull FileEditor editor) {
Disposer.dispose(editor);
}
@NotNull
@Override
public FileEditorState readState(@NotNull Element sourceElement, @NotNull Project project, @NotNull VirtualFile file) {
return FileEditorState.INSTANCE;
}
@Override
public void writeState(@NotNull FileEditorState state, @NotNull Project project, @NotNull Element targetElement) {
}
@NotNull
@Override
public String getEditorTypeId() {
return "terminal-session-editor";
}
@NotNull
@Override
public FileEditorPolicy getPolicy() {
return FileEditorPolicy.HIDE_DEFAULT_EDITOR;
}
}
@@ -0,0 +1,42 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.terminal.vfs;
import com.intellij.openapi.fileTypes.ex.FakeFileType;
import com.intellij.openapi.vfs.VirtualFile;
import org.jetbrains.annotations.NotNull;
/**
* @author traff
*/
public class TerminalSessionFileType extends FakeFileType {
public final static TerminalSessionFileType INSTANCE = new TerminalSessionFileType();
@NotNull
public String getName() {
return "Terminal Session";
}
@NotNull
public String getDescription() {
return getName() + " Fake File Type";
}
public boolean isMyFileType(VirtualFile file) {
return file instanceof TerminalSessionVirtualFileImpl;
}
}
@@ -0,0 +1,49 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
/**
* @author cdr
*/
package org.jetbrains.plugins.terminal.vfs;
import com.intellij.testFramework.LightVirtualFile;
import com.jediterm.terminal.ui.JediTermWidget;
import org.jetbrains.annotations.NotNull;
/**
* @author traff
*/
public class TerminalSessionVirtualFileImpl extends LightVirtualFile {
private final JediTermWidget myTerminal;
private final String myName;
public TerminalSessionVirtualFileImpl(String name, JediTermWidget terminal) {
myName = name;
myTerminal = terminal;
setFileType(TerminalSessionFileType.INSTANCE);
setWritable(true);
}
public JediTermWidget getTerminal() {
return myTerminal;
}
@NotNull
public String getName() {
return myName;
}
}
@@ -0,0 +1,128 @@
/*
* Copyright 2000-2013 JetBrains s.r.o.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.jetbrains.plugins.terminal.vfs;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.project.Project;
import com.intellij.openapi.project.ProjectManager;
import com.intellij.openapi.project.ProjectManagerAdapter;
import com.intellij.openapi.startup.StartupManager;
import com.intellij.openapi.vfs.VirtualFile;
import com.intellij.openapi.vfs.VirtualFileManager;
import com.intellij.openapi.vfs.ex.dummy.DummyFileSystem;
import com.intellij.util.containers.BidirectionalMap;
import com.intellij.util.containers.HashMap;
import org.jetbrains.annotations.NonNls;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import java.io.IOException;
import java.util.Map;
/**
* @author traff
*/
public class TerminalSessionsVFS extends DummyFileSystem {
@NonNls private static final String PROTOCOL = "terminalDummy";
@NonNls private static final String PATH_PREFIX = "terminal";
@NonNls private static final String PROTOCOL_SEPARATOR = ":/";
private final BidirectionalMap<Project, String> myProject2Id = new BidirectionalMap<Project, String>();
private final Map<String, VirtualFile> myCachedFiles = new HashMap<String, VirtualFile>();
private ProjectManagerAdapter myProjectManagerListener;
public static TerminalSessionsVFS getTerminalSessionVFS() {
return (TerminalSessionsVFS)VirtualFileManager.getInstance().getFileSystem(PROTOCOL);
}
@Override
@Nullable
public VirtualFile createRoot(String name) {
return null;
}
public void initListener() {
if (myProjectManagerListener == null || ApplicationManager.getApplication().isUnitTestMode()) {
myCachedFiles.clear();
myProject2Id.clear();
for (Project project : ProjectManager.getInstance().getOpenProjects()) {
onProjectOpened(project);
}
}
if (myProjectManagerListener == null) {
myProjectManagerListener = new ProjectManagerAdapter() {
@Override
public void projectOpened(final Project project) {
onProjectOpened(project);
}
@Override
public void projectClosed(final Project project) {
onProjectClosed(project);
}
};
ProjectManager.getInstance().addProjectManagerListener(myProjectManagerListener);
}
}
public void onProjectClosed(final Project project) {
myCachedFiles.clear();
myProject2Id.remove(project);
}
public void onProjectOpened(final Project project) {
myProject2Id.put(project, project.getLocationHash());
StartupManager.getInstance(project).runWhenProjectIsInitialized(new Runnable() {
@Override
public void run() {
// DatabaseEditorHelper.installEditorFactoryListener(project); TODO:?
}
});
}
@Override
public VirtualFile findFileByPath(@NotNull String path) {
throw new UnsupportedOperationException("not implemented yet"); //TODO: implement TerminalSessionManager and store there terminal sessions by handle ID
}
public static String getPath(Project project, final String dataSourceId, final String tableName, String typeName) {
return PATH_PREFIX + typeName + PROTOCOL_SEPARATOR + project.getLocationHash() + "/" + dataSourceId + "/" + tableName;
}
@Override
@NotNull
public String getProtocol() {
return PROTOCOL;
}
@Override
public boolean isReadOnly() {
return false;
}
@Override
public void renameFile(Object requestor, @NotNull VirtualFile vFile, @NotNull String newName) throws IOException {
throw new UnsupportedOperationException("renameFile not supported");
}
@Override
@NotNull
public String extractPresentableUrl(@NotNull String path) {
VirtualFile file = findFileByPath(path);
return file != null ? file.getPresentableName() : super.extractPresentableUrl(path);
}
}