package org.jetbrains.plugins.terminal; import com.intellij.execution.TaskExecutor; import com.intellij.execution.configurations.EncodingEnvironmentUtil; import com.intellij.execution.process.*; import com.intellij.openapi.application.Application; import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.diagnostic.Logger; import com.intellij.openapi.project.Project; import com.intellij.openapi.roots.ProjectRootManager; import com.intellij.openapi.util.SystemInfo; import com.intellij.openapi.vfs.VirtualFile; import com.intellij.util.Consumer; import com.intellij.util.containers.HashMap; import com.jediterm.pty.PtyProcessTtyConnector; import com.jediterm.terminal.TtyConnector; import com.pty4j.PtyProcess; import com.pty4j.util.PtyUtil; import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; import java.io.File; import java.io.IOException; import java.io.OutputStream; import java.nio.charset.Charset; import java.util.Map; import java.util.concurrent.ExecutionException; import java.util.concurrent.Future; /** * @author traff */ public class LocalTerminalDirectRunner extends AbstractTerminalRunner { private static final Logger LOG = Logger.getInstance(LocalTerminalDirectRunner.class); private final Charset myDefaultCharset; public LocalTerminalDirectRunner(Project project) { super(project); myDefaultCharset = Charset.forName("UTF-8"); } private static boolean hasLoginArgument(String name) { return name.equals("bash") || name.equals("sh") || name.equals("zsh"); } private static String getShellName(String path) { return new File(path).getName(); } private static File findRCFile() { try { final String folder = PtyUtil.getPtyLibFolderPath(); if (folder != null) { File rcFile = new File(folder, "jediterm.in"); if (rcFile.exists()) { return rcFile; } } } catch (Exception e) { LOG.warn("Unable to get JAR folder", e); } return null; } @NotNull public static LocalTerminalDirectRunner createTerminalRunner(Project project) { return new LocalTerminalDirectRunner(project); } @Override protected PtyProcess createProcess(@Nullable String directory) throws ExecutionException { Map envs = new HashMap(System.getenv()); envs.put("TERM", "xterm-256color"); EncodingEnvironmentUtil.fixDefaultEncodingIfMac(envs, getProject()); try { return PtyProcess.exec(getCommand(), envs, directory != null ? directory : currentProjectFolder()); } catch (IOException e) { throw new ExecutionException(e); } } private String currentProjectFolder() { final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(myProject); final VirtualFile[] roots = projectRootManager.getContentRoots(); if (roots.length == 1) { roots[0].getCanonicalPath(); } final VirtualFile baseDir = myProject.getBaseDir(); return baseDir == null ? null : baseDir.getCanonicalPath(); } @Override protected ProcessHandler createProcessHandler(final PtyProcess process) { return new PtyProcessHandler(process); } @Override protected TtyConnector createTtyConnector(PtyProcess process) { return new PtyProcessTtyConnector(process, myDefaultCharset); } @Override protected String getTerminalConnectionName(PtyProcess process) { return "Local Terminal"; } public String[] getCommand() { String[] command; String shellPath = TerminalOptionsProvider.getInstance().getShellPath(); if (SystemInfo.isUnix) { File rcFile = findRCFile(); String shellName = getShellName(shellPath); if (rcFile != null && (shellName.equals("bash") || shellName.equals("sh"))) { command = new String[]{shellPath, "--rcfile", rcFile.getAbsolutePath(), "-i"}; } else if (hasLoginArgument(shellName)) { command = new String[]{shellPath, "--login"}; } else { command = shellPath.split(" "); } } else { command = new String[]{shellPath}; } return command; } private static class PtyProcessHandler extends ProcessHandler implements TaskExecutor { private final PtyProcess myProcess; private final ProcessWaitFor myWaitFor; public PtyProcessHandler(PtyProcess process) { myProcess = process; myWaitFor = new ProcessWaitFor(process, this); } @Override public void startNotify() { addProcessListener(new ProcessAdapter() { @Override public void startNotified(ProcessEvent event) { try { myWaitFor.setTerminationCallback(new Consumer() { @Override public void consume(Integer integer) { notifyProcessTerminated(integer); } }); } finally { removeProcessListener(this); } } }); super.startNotify(); } @Override protected void destroyProcessImpl() { myProcess.destroy(); } @Override protected void detachProcessImpl() { destroyProcessImpl(); } @Override public boolean detachIsDefault() { return false; } @Override public boolean isSilentlyDestroyOnClose() { return true; } @Nullable @Override public OutputStream getProcessInput() { return myProcess.getOutputStream(); } @Override public Future executeTask(Runnable task) { return executeOnPooledThread(task); } protected static Future executeOnPooledThread(Runnable task) { final Application application = ApplicationManager.getApplication(); if (application != null) { return application.executeOnPooledThread(task); } return BaseOSProcessHandler.ExecutorServiceHolder.submit(task); } } }