Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 1d4c3fa904 | |||
| d54e9dd058 | |||
| 792ea03808 |
@ -14,6 +14,7 @@ dependencies {
|
|||||||
// IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
|
// IntelliJ Platform Gradle Plugin Dependencies Extension - read more: https://plugins.jetbrains.com/docs/intellij/tools-intellij-platform-gradle-plugin-dependencies-extension.html
|
||||||
intellijPlatform {
|
intellijPlatform {
|
||||||
intellijIdeaCommunity("2024.1.7")
|
intellijIdeaCommunity("2024.1.7")
|
||||||
|
bundledPlugin("org.jetbrains.plugins.terminal")
|
||||||
testFramework(TestFrameworkType.Platform)
|
testFramework(TestFrameworkType.Platform)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -11,7 +11,6 @@ import org.jetbrains.annotations.Nullable;
|
|||||||
|
|
||||||
import javax.swing.*;
|
import javax.swing.*;
|
||||||
import java.awt.event.MouseEvent;
|
import java.awt.event.MouseEvent;
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -36,7 +35,6 @@ public class ChooseServerWidget implements StatusBarWidget,
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- IconPresentation ----------
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable Icon getIcon() {
|
public @Nullable Icon getIcon() {
|
||||||
return null;
|
return null;
|
||||||
@ -76,7 +74,6 @@ public class ChooseServerWidget implements StatusBarWidget,
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- TextPresentation ----------
|
|
||||||
@Override
|
@Override
|
||||||
public @Nullable String getText() {
|
public @Nullable String getText() {
|
||||||
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(project.getBasePath());
|
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(project.getBasePath());
|
||||||
@ -92,12 +89,10 @@ public class ChooseServerWidget implements StatusBarWidget,
|
|||||||
return JComponent.CENTER_ALIGNMENT;
|
return JComponent.CENTER_ALIGNMENT;
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Логика переключения ----------
|
|
||||||
private void switchToServer(SshServer server) {
|
private void switchToServer(SshServer server) {
|
||||||
SshServerManager.getInstance().setCurrentServerId(server.id);
|
SshServerManager.getInstance().setCurrentServerId(server.id);
|
||||||
|
|
||||||
String localPath = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
String localPath = SshServerManager.getLocalProjectPath(server);
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath)).toString().replace('\\', '/');
|
|
||||||
|
|
||||||
for (Project openProject : ProjectManager.getInstance().getOpenProjects()) {
|
for (Project openProject : ProjectManager.getInstance().getOpenProjects()) {
|
||||||
if (openProject.getBasePath() != null && openProject.getBasePath().equals(localPath)) {
|
if (openProject.getBasePath() != null && openProject.getBasePath().equals(localPath)) {
|
||||||
@ -118,8 +113,4 @@ public class ChooseServerWidget implements StatusBarWidget,
|
|||||||
com.intellij.openapi.options.ShowSettingsUtil.getInstance()
|
com.intellij.openapi.options.ShowSettingsUtil.getInstance()
|
||||||
.showSettingsDialog(project, SshServerConfigurable.class);
|
.showSettingsDialog(project, SshServerConfigurable.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sanitize(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -16,33 +16,25 @@ import com.jcraft.jsch.*;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import org.jdom.JDOMException;
|
import org.jdom.JDOMException;
|
||||||
import java.io.File;
|
|
||||||
|
|
||||||
import java.io.*;
|
import java.io.*;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.nio.file.Paths;
|
import java.nio.file.Paths;
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.Vector;
|
import java.util.Vector;
|
||||||
|
|
||||||
public class ConnectToRemoteAction extends AnAction {
|
public class ConnectToRemoteAction extends AnAction {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void actionPerformed(@NotNull AnActionEvent e) {
|
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||||
// Показываем окно настройки серверов
|
|
||||||
ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), SshServerConfigurable.class);
|
ShowSettingsUtil.getInstance().showSettingsDialog(e.getProject(), SshServerConfigurable.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Статический метод для открытия проекта (используется из виджета и действия)
|
|
||||||
public static void openProjectForServer(SshServer server) {
|
public static void openProjectForServer(SshServer server) {
|
||||||
String localPath = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
String localPath = SshServerManager.getLocalProjectPath(server);
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath)).toString().replace('\\', '/');
|
|
||||||
|
|
||||||
// Если папка уже существует, просто открываем
|
|
||||||
File localDir = new File(localPath);
|
File localDir = new File(localPath);
|
||||||
if (localDir.exists() && localDir.listFiles().length > 0) {
|
if (localDir.exists() && localDir.listFiles().length > 0) {
|
||||||
openExistingProject(localPath, server.id);
|
openExistingProject(localPath, server.id);
|
||||||
} else {
|
} else {
|
||||||
// Скачиваем и открываем
|
|
||||||
downloadAndOpen(localPath, server);
|
downloadAndOpen(localPath, server);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -74,6 +66,28 @@ public class ConnectToRemoteAction extends AnAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static void syncProject(SshServer server, String localPath, ProgressIndicator indicator) throws Exception {
|
||||||
|
JSch jsch = new JSch();
|
||||||
|
Session session = jsch.getSession(server.user, server.host, server.port);
|
||||||
|
session.setPassword(SshServerManager.getInstance().getPassword(server.id));
|
||||||
|
java.util.Properties config = new java.util.Properties();
|
||||||
|
config.put("StrictHostKeyChecking", "no");
|
||||||
|
session.setConfig(config);
|
||||||
|
session.connect(5000);
|
||||||
|
|
||||||
|
ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");
|
||||||
|
sftp.connect(3000);
|
||||||
|
|
||||||
|
try {
|
||||||
|
File localDir = new File(localPath);
|
||||||
|
if (!localDir.exists()) localDir.mkdirs();
|
||||||
|
syncDir(sftp, server.remoteProjectPath, localDir, indicator);
|
||||||
|
} finally {
|
||||||
|
sftp.disconnect();
|
||||||
|
session.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void downloadProject(ProgressIndicator indicator, String localPath, SshServer server) throws Exception {
|
private static void downloadProject(ProgressIndicator indicator, String localPath, SshServer server) throws Exception {
|
||||||
JSch jsch = new JSch();
|
JSch jsch = new JSch();
|
||||||
Session session = jsch.getSession(server.user, server.host, server.port);
|
Session session = jsch.getSession(server.user, server.host, server.port);
|
||||||
@ -116,6 +130,49 @@ public class ConnectToRemoteAction extends AnAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void syncDir(ChannelSftp sftp, String remoteDir, File localDir, ProgressIndicator indicator) {
|
||||||
|
try {
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
Vector<ChannelSftp.LsEntry> entries = sftp.ls(remoteDir);
|
||||||
|
for (ChannelSftp.LsEntry entry : entries) {
|
||||||
|
if (indicator.isCanceled()) break;
|
||||||
|
|
||||||
|
String filename = entry.getFilename();
|
||||||
|
if (filename.equals(".") || filename.equals("..")) continue;
|
||||||
|
|
||||||
|
String remotePath = remoteDir + "/" + filename;
|
||||||
|
try {
|
||||||
|
if (entry.getAttrs().isDir()) {
|
||||||
|
File subDir = new File(localDir, filename);
|
||||||
|
if (!subDir.exists()) subDir.mkdirs();
|
||||||
|
syncDir(sftp, remotePath, subDir, indicator);
|
||||||
|
} else {
|
||||||
|
File localFile = new File(localDir, filename);
|
||||||
|
boolean needDownload = !localFile.exists();
|
||||||
|
if (!needDownload && localFile.isFile()) {
|
||||||
|
long remoteSize = entry.getAttrs().getSize();
|
||||||
|
long remoteMtime = entry.getAttrs().getMTime();
|
||||||
|
if (localFile.length() != remoteSize || localFile.lastModified() / 1000L != remoteMtime) {
|
||||||
|
needDownload = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (needDownload) {
|
||||||
|
indicator.setText("Syncing: " + filename);
|
||||||
|
sftp.get(remotePath, localFile.getAbsolutePath());
|
||||||
|
if (entry.getAttrs().getMTime() > 0) {
|
||||||
|
localFile.setLastModified(entry.getAttrs().getMTime() * 1000L);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
System.err.println("Skip entry: " + remotePath + " - " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SftpException e) {
|
||||||
|
System.err.println("Cannot list directory: " + remoteDir + " - " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static void createProjectStructure(File projectDir) throws IOException {
|
private static void createProjectStructure(File projectDir) throws IOException {
|
||||||
File ideaDir = new File(projectDir, ".idea");
|
File ideaDir = new File(projectDir, ".idea");
|
||||||
if (!ideaDir.exists()) ideaDir.mkdirs();
|
if (!ideaDir.exists()) ideaDir.mkdirs();
|
||||||
@ -163,75 +220,4 @@ public class ConnectToRemoteAction extends AnAction {
|
|||||||
writer.write(content);
|
writer.write(content);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String sanitize(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public static void syncProject(SshServer server, String localPath, ProgressIndicator indicator) throws Exception {
|
|
||||||
JSch jsch = new JSch();
|
|
||||||
Session session = jsch.getSession(server.user, server.host, server.port);
|
|
||||||
session.setPassword(SshServerManager.getInstance().getPassword(server.id));
|
|
||||||
java.util.Properties config = new java.util.Properties();
|
|
||||||
config.put("StrictHostKeyChecking", "no");
|
|
||||||
session.setConfig(config);
|
|
||||||
session.connect(5000);
|
|
||||||
|
|
||||||
ChannelSftp sftp = (ChannelSftp) session.openChannel("sftp");
|
|
||||||
sftp.connect(3000);
|
|
||||||
|
|
||||||
try {
|
|
||||||
File localDir = new File(localPath);
|
|
||||||
if (!localDir.exists()) localDir.mkdirs();
|
|
||||||
syncDir(sftp, server.remoteProjectPath, localDir, indicator);
|
|
||||||
} finally {
|
|
||||||
sftp.disconnect();
|
|
||||||
session.disconnect();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void syncDir(ChannelSftp sftp, String remoteDir, File localDir, ProgressIndicator indicator) {
|
|
||||||
try {
|
|
||||||
@SuppressWarnings("unchecked")
|
|
||||||
Vector<ChannelSftp.LsEntry> entries = sftp.ls(remoteDir);
|
|
||||||
for (ChannelSftp.LsEntry entry : entries) {
|
|
||||||
if (indicator.isCanceled()) break;
|
|
||||||
|
|
||||||
String filename = entry.getFilename();
|
|
||||||
if (filename.equals(".") || filename.equals("..")) continue;
|
|
||||||
|
|
||||||
String remotePath = remoteDir + "/" + filename;
|
|
||||||
try {
|
|
||||||
if (entry.getAttrs().isDir()) {
|
|
||||||
File subDir = new File(localDir, filename);
|
|
||||||
if (!subDir.exists()) subDir.mkdirs();
|
|
||||||
syncDir(sftp, remotePath, subDir, indicator);
|
|
||||||
} else {
|
|
||||||
File localFile = new File(localDir, filename);
|
|
||||||
boolean needDownload = !localFile.exists();
|
|
||||||
if (!needDownload && localFile.isFile()) {
|
|
||||||
long remoteSize = entry.getAttrs().getSize();
|
|
||||||
long remoteMtime = entry.getAttrs().getMTime();
|
|
||||||
if (localFile.length() != remoteSize || localFile.lastModified() / 1000L != remoteMtime) {
|
|
||||||
needDownload = true;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (needDownload) {
|
|
||||||
indicator.setText("Syncing: " + filename);
|
|
||||||
sftp.get(remotePath, localFile.getAbsolutePath());
|
|
||||||
if (entry.getAttrs().getMTime() > 0) {
|
|
||||||
localFile.setLastModified(entry.getAttrs().getMTime() * 1000L);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Логируем и пропускаем файл, который не удалось скачать/записать
|
|
||||||
System.err.println("Skip entry: " + remotePath + " - " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} catch (SftpException e) {
|
|
||||||
System.err.println("Cannot list directory: " + remoteDir + " - " + e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -0,0 +1,88 @@
|
|||||||
|
package com.example.plugin;
|
||||||
|
|
||||||
|
import com.intellij.openapi.Disposable;
|
||||||
|
import com.intellij.openapi.actionSystem.AnAction;
|
||||||
|
import com.intellij.openapi.actionSystem.AnActionEvent;
|
||||||
|
import com.intellij.openapi.project.Project;
|
||||||
|
import com.intellij.openapi.ui.Messages;
|
||||||
|
import com.intellij.openapi.util.Disposer;
|
||||||
|
import com.intellij.openapi.wm.ToolWindow;
|
||||||
|
import com.intellij.openapi.wm.ToolWindowManager;
|
||||||
|
import com.intellij.terminal.JBTerminalWidget;
|
||||||
|
import com.intellij.ui.content.Content;
|
||||||
|
import com.intellij.ui.content.ContentFactory;
|
||||||
|
import com.jcraft.jsch.ChannelShell;
|
||||||
|
import com.jediterm.terminal.TtyConnector;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.plugins.terminal.JBTerminalSystemSettingsProvider;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class OpenRemoteTerminalAction extends AnAction {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void actionPerformed(@NotNull AnActionEvent e) {
|
||||||
|
Project project = e.getProject();
|
||||||
|
if (project == null) return;
|
||||||
|
|
||||||
|
String serverId = ProjectServerMapping.getInstance()
|
||||||
|
.getServerIdForProject(project.getBasePath());
|
||||||
|
|
||||||
|
if (serverId == null) {
|
||||||
|
Messages.showErrorDialog("No server associated with this project.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Optional<SshServer> maybeServer =
|
||||||
|
SshServerManager.getInstance().findServer(serverId);
|
||||||
|
|
||||||
|
if (maybeServer.isEmpty()) {
|
||||||
|
Messages.showErrorDialog("Server not found.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
SshServer server = maybeServer.get();
|
||||||
|
|
||||||
|
try {
|
||||||
|
ChannelShell shellChannel =
|
||||||
|
SftpSessionManager.getInstance(project)
|
||||||
|
.getShellChannel(server);
|
||||||
|
|
||||||
|
TtyConnector connector = new SshTtyConnector(shellChannel, server.remoteProjectPath);
|
||||||
|
|
||||||
|
ToolWindow terminalToolWindow =
|
||||||
|
ToolWindowManager.getInstance(project).getToolWindow("Terminal");
|
||||||
|
|
||||||
|
if (terminalToolWindow == null) {
|
||||||
|
Messages.showErrorDialog("Terminal tool window not found.", "Error");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Disposable disposable =
|
||||||
|
Disposer.newDisposable(project, "Remote SSH Terminal");
|
||||||
|
|
||||||
|
JBTerminalWidget widget =
|
||||||
|
new JBTerminalWidget(
|
||||||
|
project,
|
||||||
|
new JBTerminalSystemSettingsProvider(),
|
||||||
|
disposable
|
||||||
|
);
|
||||||
|
|
||||||
|
Content content =
|
||||||
|
ContentFactory.getInstance()
|
||||||
|
.createContent(widget, "Remote: " + server.name, false);
|
||||||
|
|
||||||
|
content.setDisposer(disposable);
|
||||||
|
terminalToolWindow.getContentManager().addContent(content);
|
||||||
|
terminalToolWindow.getContentManager().setSelectedContent(content, true);
|
||||||
|
|
||||||
|
terminalToolWindow.activate(() -> widget.start(connector), true);
|
||||||
|
|
||||||
|
} catch (Exception ex) {
|
||||||
|
Messages.showErrorDialog(
|
||||||
|
"Failed to open terminal: " + ex.getMessage(),
|
||||||
|
"Error"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -13,7 +13,6 @@ import com.intellij.openapi.vfs.VfsUtil;
|
|||||||
import com.intellij.openapi.vfs.VirtualFile;
|
import com.intellij.openapi.vfs.VirtualFile;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class RefreshFromServerAction extends AnAction {
|
public class RefreshFromServerAction extends AnAction {
|
||||||
@ -42,12 +41,9 @@ public class RefreshFromServerAction extends AnAction {
|
|||||||
@Override
|
@Override
|
||||||
public void run(@NotNull ProgressIndicator indicator) {
|
public void run(@NotNull ProgressIndicator indicator) {
|
||||||
try {
|
try {
|
||||||
String localRoot = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
String localRoot = SshServerManager.getLocalProjectPath(server);
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath))
|
|
||||||
.toString().replace('\\', '/');
|
|
||||||
ConnectToRemoteAction.syncProject(server, localRoot, indicator);
|
ConnectToRemoteAction.syncProject(server, localRoot, indicator);
|
||||||
|
|
||||||
// Обновляем виртуальную файловую систему, чтобы IDE увидела изменения
|
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
ApplicationManager.getApplication().invokeLater(() -> {
|
||||||
VirtualFile rootDir = LocalFileSystem.getInstance().findFileByPath(localRoot);
|
VirtualFile rootDir = LocalFileSystem.getInstance().findFileByPath(localRoot);
|
||||||
if (rootDir != null) {
|
if (rootDir != null) {
|
||||||
@ -62,8 +58,4 @@ public class RefreshFromServerAction extends AnAction {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sanitize(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -4,11 +4,11 @@ import com.intellij.openapi.project.Project;
|
|||||||
import com.jcraft.jsch.*;
|
import com.jcraft.jsch.*;
|
||||||
|
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.atomic.AtomicReference;
|
|
||||||
|
|
||||||
public class SftpSessionManager {
|
public class SftpSessionManager {
|
||||||
private final ConcurrentHashMap<String, ChannelSftp> channels = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, ChannelSftp> channels = new ConcurrentHashMap<>();
|
||||||
private final ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();
|
private final ConcurrentHashMap<String, Session> sessions = new ConcurrentHashMap<>();
|
||||||
|
private final ConcurrentHashMap<String, ChannelShell> shellChannels = new ConcurrentHashMap<>();
|
||||||
private final Object lock = new Object();
|
private final Object lock = new Object();
|
||||||
|
|
||||||
public ChannelSftp getChannel(SshServer server) throws Exception {
|
public ChannelSftp getChannel(SshServer server) throws Exception {
|
||||||
@ -20,32 +20,59 @@ public class SftpSessionManager {
|
|||||||
channel = channels.get(server.id);
|
channel = channels.get(server.id);
|
||||||
if (channel != null && channel.isConnected() && !channel.isClosed()) return channel;
|
if (channel != null && channel.isConnected() && !channel.isClosed()) return channel;
|
||||||
|
|
||||||
JSch jsch = new JSch();
|
Session session = getSession(server);
|
||||||
Session session = jsch.getSession(server.user, server.host, server.port);
|
|
||||||
session.setPassword(SshServerManager.getInstance().getPassword(server.id));
|
|
||||||
java.util.Properties config = new java.util.Properties();
|
|
||||||
config.put("StrictHostKeyChecking", "no");
|
|
||||||
session.setConfig(config);
|
|
||||||
session.connect(5000);
|
|
||||||
|
|
||||||
ChannelSftp newChannel = (ChannelSftp) session.openChannel("sftp");
|
ChannelSftp newChannel = (ChannelSftp) session.openChannel("sftp");
|
||||||
newChannel.connect(3000);
|
newChannel.connect(3000);
|
||||||
|
|
||||||
sessions.put(server.id, session);
|
|
||||||
channels.put(server.id, newChannel);
|
channels.put(server.id, newChannel);
|
||||||
return newChannel;
|
return newChannel;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public ChannelShell getShellChannel(SshServer server) throws Exception {
|
||||||
|
ChannelShell shell = shellChannels.get(server.id);
|
||||||
|
if (shell != null && shell.isConnected() && !shell.isClosed()) {
|
||||||
|
return shell;
|
||||||
|
}
|
||||||
|
synchronized (lock) {
|
||||||
|
shell = shellChannels.get(server.id);
|
||||||
|
if (shell != null && shell.isConnected() && !shell.isClosed()) return shell;
|
||||||
|
|
||||||
|
Session session = getSession(server);
|
||||||
|
ChannelShell newShell = (ChannelShell) session.openChannel("shell");
|
||||||
|
newShell.connect(5000);
|
||||||
|
shellChannels.put(server.id, newShell);
|
||||||
|
return newShell;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private Session getSession(SshServer server) throws JSchException {
|
||||||
|
Session session = sessions.get(server.id);
|
||||||
|
if (session == null || !session.isConnected()) {
|
||||||
|
JSch jsch = new JSch();
|
||||||
|
session = jsch.getSession(server.user, server.host, server.port);
|
||||||
|
session.setPassword(SshServerManager.getInstance().getPassword(server.id));
|
||||||
|
java.util.Properties config = new java.util.Properties();
|
||||||
|
config.put("StrictHostKeyChecking", "no");
|
||||||
|
session.setConfig(config);
|
||||||
|
session.connect(5000);
|
||||||
|
sessions.put(server.id, session);
|
||||||
|
}
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
|
||||||
public void disconnect() {
|
public void disconnect() {
|
||||||
synchronized (lock) {
|
synchronized (lock) {
|
||||||
|
for (ChannelShell sh : shellChannels.values()) {
|
||||||
|
if (sh.isConnected()) sh.disconnect();
|
||||||
|
}
|
||||||
|
shellChannels.clear();
|
||||||
for (ChannelSftp ch : channels.values()) {
|
for (ChannelSftp ch : channels.values()) {
|
||||||
if (ch.isConnected()) ch.disconnect();
|
if (ch.isConnected()) ch.disconnect();
|
||||||
}
|
}
|
||||||
|
channels.clear();
|
||||||
for (Session s : sessions.values()) {
|
for (Session s : sessions.values()) {
|
||||||
if (s.isConnected()) s.disconnect();
|
if (s.isConnected()) s.disconnect();
|
||||||
}
|
}
|
||||||
channels.clear();
|
|
||||||
sessions.clear();
|
sessions.clear();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -189,6 +189,11 @@ class ServerDialog extends DialogWrapper {
|
|||||||
result.add(new ValidationInfo("Invalid port", portField));
|
result.add(new ValidationInfo("Invalid port", portField));
|
||||||
}
|
}
|
||||||
if (remotePathField.getText().trim().isEmpty()) result.add(new ValidationInfo("Remote path required", remotePathField));
|
if (remotePathField.getText().trim().isEmpty()) result.add(new ValidationInfo("Remote path required", remotePathField));
|
||||||
|
|
||||||
|
// Требуем пароль для нового сервера
|
||||||
|
if (SshServerManager.getInstance().findServer(server.id).isEmpty() && passwordField.getPassword().length == 0) {
|
||||||
|
result.add(new ValidationInfo("Password required for new server", passwordField));
|
||||||
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -206,7 +211,7 @@ class ServerDialog extends DialogWrapper {
|
|||||||
if (!pass.isEmpty()) {
|
if (!pass.isEmpty()) {
|
||||||
SshServerManager.getInstance().setPassword(server.id, pass);
|
SshServerManager.getInstance().setPassword(server.id, pass);
|
||||||
}
|
}
|
||||||
// Если поле пустое – оставляем старый пароль (если есть) нетронутым
|
// Если пароль пустой и сервер уже существует – старый пароль остаётся без изменений
|
||||||
super.doOKAction();
|
super.doOKAction();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -9,6 +9,7 @@ import com.intellij.util.xmlb.XmlSerializerUtil;
|
|||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
import org.jetbrains.annotations.Nullable;
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.nio.file.Paths;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
@ -56,7 +57,6 @@ public class SshServerManager implements PersistentStateComponent<SshServerManag
|
|||||||
if (id.equals(myState.currentServerId)) {
|
if (id.equals(myState.currentServerId)) {
|
||||||
myState.currentServerId = null;
|
myState.currentServerId = null;
|
||||||
}
|
}
|
||||||
// Удаляем сохранённый пароль
|
|
||||||
PasswordSafe.getInstance().setPassword(createCredentialAttributes(id), null);
|
PasswordSafe.getInstance().setPassword(createCredentialAttributes(id), null);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -103,4 +103,22 @@ public class SshServerManager implements PersistentStateComponent<SshServerManag
|
|||||||
return com.intellij.openapi.application.ApplicationManager.getApplication()
|
return com.intellij.openapi.application.ApplicationManager.getApplication()
|
||||||
.getService(SshServerManager.class);
|
.getService(SshServerManager.class);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---------- Вспомогательные статические методы ----------
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Возвращает локальный путь к папке проекта на основе хоста и имени сервера.
|
||||||
|
*/
|
||||||
|
public static String getLocalProjectPath(SshServer server) {
|
||||||
|
return Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
||||||
|
sanitize(server.host), sanitize(server.name))
|
||||||
|
.toString().replace('\\', '/');
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Заменяет в строке все символы, кроме букв, цифр, точки и дефиса, на '_'.
|
||||||
|
*/
|
||||||
|
public static String sanitize(String input) {
|
||||||
|
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@ -13,8 +13,8 @@ import com.intellij.openapi.vfs.*;
|
|||||||
import com.jcraft.jsch.*;
|
import com.jcraft.jsch.*;
|
||||||
import org.jetbrains.annotations.NotNull;
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
|
||||||
import java.nio.file.Paths;
|
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
import java.util.Vector;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
|
||||||
@ -35,7 +35,7 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Слушатель изменений файлов для синхронизации
|
// Слушатель изменений файлов для синхронизации (создание, изменение, удаление)
|
||||||
VirtualFile baseDir = project.getBaseDir();
|
VirtualFile baseDir = project.getBaseDir();
|
||||||
if (baseDir != null) {
|
if (baseDir != null) {
|
||||||
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
|
VirtualFileManager.getInstance().addVirtualFileListener(new VirtualFileListener() {
|
||||||
@ -49,6 +49,11 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
syncFile(event.getFile(), project);
|
syncFile(event.getFile(), project);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void fileDeleted(@NotNull VirtualFileEvent event) {
|
||||||
|
deleteRemoteFile(event.getFile(), project);
|
||||||
|
}
|
||||||
|
|
||||||
private void syncFile(VirtualFile file, Project project) {
|
private void syncFile(VirtualFile file, Project project) {
|
||||||
if (!file.isDirectory()) {
|
if (!file.isDirectory()) {
|
||||||
uploadFile(file, project);
|
uploadFile(file, project);
|
||||||
@ -65,18 +70,13 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
Optional<SshServer> maybeServer = SshServerManager.getInstance().findServer(serverId);
|
Optional<SshServer> maybeServer = SshServerManager.getInstance().findServer(serverId);
|
||||||
if (maybeServer.isPresent()) {
|
if (maybeServer.isPresent()) {
|
||||||
SshServer server = maybeServer.get();
|
SshServer server = maybeServer.get();
|
||||||
// Модальный диалог – блокирует IDE до завершения синхронизации
|
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
ApplicationManager.getApplication().invokeLater(() -> {
|
||||||
ProgressManager.getInstance().run(new Task.Modal(project, "Synchronizing with " + server.name, true) {
|
ProgressManager.getInstance().run(new Task.Modal(project, "Synchronizing with " + server.name, true) {
|
||||||
@Override
|
@Override
|
||||||
public void run(@NotNull ProgressIndicator indicator) {
|
public void run(@NotNull ProgressIndicator indicator) {
|
||||||
try {
|
try {
|
||||||
String localRoot = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
String localRoot = SshServerManager.getLocalProjectPath(server);
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath))
|
|
||||||
.toString().replace('\\', '/');
|
|
||||||
ConnectToRemoteAction.syncProject(server, localRoot, indicator);
|
ConnectToRemoteAction.syncProject(server, localRoot, indicator);
|
||||||
|
|
||||||
// Обновляем виртуальную файловую систему, чтобы IDE увидела изменения
|
|
||||||
ApplicationManager.getApplication().invokeLater(() -> {
|
ApplicationManager.getApplication().invokeLater(() -> {
|
||||||
VirtualFile rootDir = LocalFileSystem.getInstance().findFileByPath(localRoot);
|
VirtualFile rootDir = LocalFileSystem.getInstance().findFileByPath(localRoot);
|
||||||
if (rootDir != null) {
|
if (rootDir != null) {
|
||||||
@ -99,7 +99,6 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
String projectPath = project.getBasePath();
|
String projectPath = project.getBasePath();
|
||||||
if (projectPath == null) return;
|
if (projectPath == null) return;
|
||||||
|
|
||||||
// Определяем привязанный сервер
|
|
||||||
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(projectPath);
|
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(projectPath);
|
||||||
if (serverId == null) return;
|
if (serverId == null) return;
|
||||||
|
|
||||||
@ -107,31 +106,24 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
if (maybeServer.isEmpty()) return;
|
if (maybeServer.isEmpty()) return;
|
||||||
SshServer server = maybeServer.get();
|
SshServer server = maybeServer.get();
|
||||||
|
|
||||||
// Вычисляем локальный корень проекта (как он был сохранен при создании)
|
String localRoot = SshServerManager.getLocalProjectPath(server);
|
||||||
String localRoot = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath))
|
|
||||||
.toString().replace('\\', '/');
|
|
||||||
|
|
||||||
String localFilePath = localFile.getPath(); // всегда с прямыми слешами
|
String localFilePath = localFile.getPath();
|
||||||
|
|
||||||
// Игнорируем служебные файлы IDE
|
|
||||||
if (localFilePath.contains("/.idea/") || localFile.getName().equals("remote.iml")) {
|
if (localFilePath.contains("/.idea/") || localFile.getName().equals("remote.iml")) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Проверяем, что файл принадлежит проекту
|
|
||||||
if (!localFilePath.startsWith(localRoot)) {
|
if (!localFilePath.startsWith(localRoot)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Относительный путь и удалённый путь
|
|
||||||
String relativePath = localFilePath.substring(localRoot.length());
|
String relativePath = localFilePath.substring(localRoot.length());
|
||||||
if (relativePath.startsWith("/")) {
|
if (relativePath.startsWith("/")) {
|
||||||
relativePath = relativePath.substring(1);
|
relativePath = relativePath.substring(1);
|
||||||
}
|
}
|
||||||
String remoteFilePath = server.remoteProjectPath + "/" + relativePath;
|
String remoteFilePath = server.remoteProjectPath + "/" + relativePath;
|
||||||
|
|
||||||
// Отправляем файл в отдельном потоке через общее SFTP-соединение
|
|
||||||
uploadExecutor.submit(() -> {
|
uploadExecutor.submit(() -> {
|
||||||
try {
|
try {
|
||||||
ChannelSftp sftp = SftpSessionManager.getInstance(project).getChannel(server);
|
ChannelSftp sftp = SftpSessionManager.getInstance(project).getChannel(server);
|
||||||
@ -149,6 +141,84 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void deleteRemoteFile(VirtualFile localFile, Project project) {
|
||||||
|
String projectPath = project.getBasePath();
|
||||||
|
if (projectPath == null) return;
|
||||||
|
|
||||||
|
String serverId = ProjectServerMapping.getInstance().getServerIdForProject(projectPath);
|
||||||
|
if (serverId == null) return;
|
||||||
|
|
||||||
|
Optional<SshServer> maybeServer = SshServerManager.getInstance().findServer(serverId);
|
||||||
|
if (maybeServer.isEmpty()) return;
|
||||||
|
SshServer server = maybeServer.get();
|
||||||
|
|
||||||
|
String localRoot = SshServerManager.getLocalProjectPath(server);
|
||||||
|
|
||||||
|
String localFilePath = localFile.getPath();
|
||||||
|
|
||||||
|
if (localFilePath.contains("/.idea/") || localFile.getName().equals("remote.iml")) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!localFilePath.startsWith(localRoot)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String relativePath = localFilePath.substring(localRoot.length());
|
||||||
|
if (relativePath.startsWith("/")) {
|
||||||
|
relativePath = relativePath.substring(1);
|
||||||
|
}
|
||||||
|
String remoteFilePath = server.remoteProjectPath + "/" + relativePath;
|
||||||
|
|
||||||
|
uploadExecutor.submit(() -> {
|
||||||
|
try {
|
||||||
|
ChannelSftp sftp = SftpSessionManager.getInstance(project).getChannel(server);
|
||||||
|
deleteRemotePath(sftp, remoteFilePath, localFile.isDirectory());
|
||||||
|
} catch (Exception e) {
|
||||||
|
ApplicationManager.getApplication().invokeLater(() ->
|
||||||
|
com.intellij.openapi.ui.Messages.showErrorDialog(
|
||||||
|
"Delete error " + localFile.getName() + ": " + e.getMessage(),
|
||||||
|
"SFTP Error"
|
||||||
|
)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
private void deleteRemotePath(ChannelSftp sftp, String remotePath, boolean isDirectory) throws Exception {
|
||||||
|
if (isDirectory) {
|
||||||
|
try {
|
||||||
|
Vector<ChannelSftp.LsEntry> entries = sftp.ls(remotePath);
|
||||||
|
for (ChannelSftp.LsEntry entry : entries) {
|
||||||
|
String name = entry.getFilename();
|
||||||
|
if (name.equals(".") || name.equals("..")) continue;
|
||||||
|
String childPath = remotePath + "/" + name;
|
||||||
|
if (entry.getAttrs().isDir()) {
|
||||||
|
deleteRemotePath(sftp, childPath, true);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
sftp.rm(childPath);
|
||||||
|
} catch (SftpException e) {
|
||||||
|
if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) throw e;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (SftpException e) {
|
||||||
|
if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) throw e;
|
||||||
|
}
|
||||||
|
sftp.rmdir(remotePath);
|
||||||
|
} else {
|
||||||
|
try {
|
||||||
|
sftp.rm(remotePath);
|
||||||
|
} catch (SftpException e) {
|
||||||
|
if (e.id != ChannelSftp.SSH_FX_NO_SUCH_FILE) {
|
||||||
|
throw e;
|
||||||
|
}
|
||||||
|
// иначе файл уже удалён – пропускаем
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void createRemoteDirs(ChannelSftp sftp, String dir) throws SftpException {
|
private void createRemoteDirs(ChannelSftp sftp, String dir) throws SftpException {
|
||||||
if (dir.isEmpty() || dir.equals("/")) return;
|
if (dir.isEmpty() || dir.equals("/")) return;
|
||||||
String[] parts = dir.split("/");
|
String[] parts = dir.split("/");
|
||||||
@ -167,8 +237,4 @@ public class SshSyncStartupActivity implements StartupActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sanitize(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
89
src/main/java/com/example/plugin/SshTtyConnector.java
Normal file
89
src/main/java/com/example/plugin/SshTtyConnector.java
Normal file
@ -0,0 +1,89 @@
|
|||||||
|
package com.example.plugin;
|
||||||
|
|
||||||
|
import com.jcraft.jsch.ChannelShell;
|
||||||
|
import com.jediterm.terminal.TtyConnector;
|
||||||
|
import org.jetbrains.annotations.NotNull;
|
||||||
|
import org.jetbrains.annotations.Nullable;
|
||||||
|
|
||||||
|
import java.awt.*;
|
||||||
|
import java.io.*;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
public class SshTtyConnector implements TtyConnector {
|
||||||
|
private final ChannelShell channel;
|
||||||
|
private final InputStream inputStream;
|
||||||
|
private final OutputStream outputStream;
|
||||||
|
private final Reader reader;
|
||||||
|
|
||||||
|
public SshTtyConnector(ChannelShell channel, String initialPath) {
|
||||||
|
this.channel = channel;
|
||||||
|
|
||||||
|
try {
|
||||||
|
this.inputStream = channel.getInputStream();
|
||||||
|
this.outputStream = channel.getOutputStream();
|
||||||
|
this.reader = new InputStreamReader(inputStream, StandardCharsets.UTF_8);
|
||||||
|
|
||||||
|
if (initialPath != null && !initialPath.isBlank()) {
|
||||||
|
outputStream.write(
|
||||||
|
("cd \"" + initialPath + "\"\n")
|
||||||
|
.getBytes(StandardCharsets.UTF_8)
|
||||||
|
);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException(e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isConnected() {
|
||||||
|
return channel.isConnected() && !channel.isClosed();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void close() {
|
||||||
|
if (channel.isConnected()) {
|
||||||
|
channel.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public @Nullable String getName() {
|
||||||
|
return "SSH Remote";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int read(char[] buf, int offset, int length) throws IOException {
|
||||||
|
return reader.read(buf, offset, length);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(String string) throws IOException {
|
||||||
|
write(string.getBytes(StandardCharsets.UTF_8));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void write(byte[] bytes) throws IOException {
|
||||||
|
outputStream.write(bytes);
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void resize(@NotNull Dimension size) {
|
||||||
|
channel.setPtySize(size.width, size.height, 0, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int waitFor() throws InterruptedException {
|
||||||
|
while (isConnected()) {
|
||||||
|
Thread.sleep(100);
|
||||||
|
}
|
||||||
|
return channel.getExitStatus();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean ready() throws IOException {
|
||||||
|
return reader.ready();
|
||||||
|
}
|
||||||
|
}
|
||||||
@ -48,11 +48,8 @@ public class SwitchServerAction extends AnAction {
|
|||||||
private void switchToServer(SshServer server, Project currentProject) {
|
private void switchToServer(SshServer server, Project currentProject) {
|
||||||
SshServerManager.getInstance().setCurrentServerId(server.id);
|
SshServerManager.getInstance().setCurrentServerId(server.id);
|
||||||
|
|
||||||
String localPath = Paths.get(System.getProperty("user.home"), "ssh-remote-projects",
|
String localPath = SshServerManager.getLocalProjectPath(server);
|
||||||
sanitize(server.host), sanitize(server.remoteProjectPath))
|
|
||||||
.toString().replace('\\', '/');
|
|
||||||
|
|
||||||
// Проверяем, открыт ли уже проект с этим путём
|
|
||||||
for (Project openProject : ProjectManager.getInstance().getOpenProjects()) {
|
for (Project openProject : ProjectManager.getInstance().getOpenProjects()) {
|
||||||
if (openProject.getBasePath() != null && openProject.getBasePath().equals(localPath)) {
|
if (openProject.getBasePath() != null && openProject.getBasePath().equals(localPath)) {
|
||||||
ApplicationManager.getApplication().invokeLater(() ->
|
ApplicationManager.getApplication().invokeLater(() ->
|
||||||
@ -61,15 +58,10 @@ public class SwitchServerAction extends AnAction {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Закрываем текущий проект и открываем новый
|
|
||||||
if (currentProject != null && !currentProject.isDisposed()) {
|
if (currentProject != null && !currentProject.isDisposed()) {
|
||||||
ProjectManager.getInstance().closeProject(currentProject);
|
ProjectManager.getInstance().closeProject(currentProject);
|
||||||
}
|
}
|
||||||
|
|
||||||
ConnectToRemoteAction.openProjectForServer(server);
|
ConnectToRemoteAction.openProjectForServer(server);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String sanitize(String input) {
|
|
||||||
return input.replaceAll("[^a-zA-Z0-9.-]", "_");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
@ -28,15 +28,14 @@
|
|||||||
]]>
|
]]>
|
||||||
</description>
|
</description>
|
||||||
<depends>com.intellij.modules.platform</depends>
|
<depends>com.intellij.modules.platform</depends>
|
||||||
|
<depends>org.jetbrains.plugins.terminal</depends>
|
||||||
|
|
||||||
<extensions defaultExtensionNs="com.intellij">
|
<extensions defaultExtensionNs="com.intellij">
|
||||||
<projectConfigurable instance="com.example.plugin.SshServerConfigurable"
|
<projectConfigurable instance="com.example.plugin.SshServerConfigurable"
|
||||||
displayName="SSH Remote Project"/>
|
displayName="SSH Remote Project"/>
|
||||||
|
|
||||||
<applicationService serviceImplementation="com.example.plugin.SshServerManager"/>
|
<applicationService serviceImplementation="com.example.plugin.SshServerManager"/>
|
||||||
<applicationService serviceImplementation="com.example.plugin.ProjectServerMapping"/>
|
<applicationService serviceImplementation="com.example.plugin.ProjectServerMapping"/>
|
||||||
<projectService serviceImplementation="com.example.plugin.SftpSessionManager"/>
|
<projectService serviceImplementation="com.example.plugin.SftpSessionManager"/>
|
||||||
<applicationService serviceImplementation="com.example.plugin.ProjectMappingService"/>
|
|
||||||
<postStartupActivity implementation="com.example.plugin.SshSyncStartupActivity"/>
|
<postStartupActivity implementation="com.example.plugin.SshSyncStartupActivity"/>
|
||||||
</extensions>
|
</extensions>
|
||||||
|
|
||||||
@ -61,6 +60,10 @@
|
|||||||
class="com.example.plugin.RemoteCommandAction"
|
class="com.example.plugin.RemoteCommandAction"
|
||||||
text="Run Remote Command"
|
text="Run Remote Command"
|
||||||
description="Execute command on the associated server"/>
|
description="Execute command on the associated server"/>
|
||||||
|
<action id="SshRemote.OpenTerminal"
|
||||||
|
class="com.example.plugin.OpenRemoteTerminalAction"
|
||||||
|
text="Open Remote Terminal"
|
||||||
|
description="Open integrated terminal connected to the remote server"/>
|
||||||
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
<add-to-group group-id="ToolsMenu" anchor="last"/>
|
||||||
</group>
|
</group>
|
||||||
</actions>
|
</actions>
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user