d7cb0abad2fa18726761e77fb6b197d8cdddf655
[idea/community.git] / plugins / terminal / src / org / jetbrains / plugins / terminal / LocalTerminalDirectRunner.java
1 /*
2  * Copyright 2000-2016 JetBrains s.r.o.
3  *
4  * Licensed under the Apache License, Version 2.0 (the "License");
5  * you may not use this file except in compliance with the License.
6  * You may obtain a copy of the License at
7  *
8  * http://www.apache.org/licenses/LICENSE-2.0
9  *
10  * Unless required by applicable law or agreed to in writing, software
11  * distributed under the License is distributed on an "AS IS" BASIS,
12  * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13  * See the License for the specific language governing permissions and
14  * limitations under the License.
15  */
16 package org.jetbrains.plugins.terminal;
17
18 import com.intellij.execution.TaskExecutor;
19 import com.intellij.execution.configurations.EncodingEnvironmentUtil;
20 import com.intellij.execution.process.ProcessAdapter;
21 import com.intellij.execution.process.ProcessEvent;
22 import com.intellij.execution.process.ProcessHandler;
23 import com.intellij.execution.process.ProcessWaitFor;
24 import com.intellij.openapi.diagnostic.Logger;
25 import com.intellij.openapi.project.Project;
26 import com.intellij.openapi.roots.ProjectRootManager;
27 import com.intellij.openapi.util.SystemInfo;
28 import com.intellij.openapi.vfs.CharsetToolkit;
29 import com.intellij.openapi.vfs.VirtualFile;
30 import com.intellij.util.Consumer;
31 import com.intellij.util.concurrency.AppExecutorUtil;
32 import com.intellij.util.containers.HashMap;
33 import com.jediterm.pty.PtyProcessTtyConnector;
34 import com.jediterm.terminal.TtyConnector;
35 import com.pty4j.PtyProcess;
36 import com.pty4j.util.PtyUtil;
37 import org.jetbrains.annotations.NotNull;
38 import org.jetbrains.annotations.Nullable;
39
40 import java.io.File;
41 import java.io.IOException;
42 import java.io.OutputStream;
43 import java.nio.charset.Charset;
44 import java.util.Map;
45 import java.util.concurrent.ExecutionException;
46 import java.util.concurrent.Future;
47
48 /**
49  * @author traff
50  */
51 public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess> {
52   private static final Logger LOG = Logger.getInstance(LocalTerminalDirectRunner.class);
53
54   private final Charset myDefaultCharset;
55
56   public LocalTerminalDirectRunner(Project project) {
57     super(project);
58     myDefaultCharset = CharsetToolkit.UTF8_CHARSET;
59   }
60
61   private static boolean hasLoginArgument(String name) {
62     return name.equals("bash") || name.equals("sh") || name.equals("zsh");
63   }
64
65   private static String getShellName(String path) {
66     return new File(path).getName();
67   }
68
69   private static File findRCFile() {
70     try {
71       final String folder = PtyUtil.getPtyLibFolderPath();
72       if (folder != null) {
73         File rcFile = new File(folder, "jediterm.in");
74         if (rcFile.exists()) {
75           return rcFile;
76         }
77       }
78     }
79     catch (Exception e) {
80       LOG.warn("Unable to get JAR folder", e);
81     }
82     return null;
83   }
84
85   @NotNull
86   public static LocalTerminalDirectRunner createTerminalRunner(Project project) {
87     return new LocalTerminalDirectRunner(project);
88   }
89
90   @Override
91   protected PtyProcess createProcess(@Nullable String directory) throws ExecutionException {
92     Map<String, String> envs = new HashMap<String, String>(System.getenv());
93     if (!SystemInfo.isWindows) {
94       envs.put("TERM", "xterm-256color");
95     }
96     EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myDefaultCharset);
97     try {
98       return PtyProcess.exec(getCommand(), envs, directory != null ? directory : currentProjectFolder());
99     }
100     catch (IOException e) {
101       throw new ExecutionException(e);
102     }
103   }
104
105   private String currentProjectFolder() {
106     final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(myProject);
107
108     final VirtualFile[] roots = projectRootManager.getContentRoots();
109     if (roots.length == 1) {
110       roots[0].getCanonicalPath();
111     }
112     final VirtualFile baseDir = myProject.getBaseDir();
113     return baseDir == null ? null : baseDir.getCanonicalPath();
114   }
115
116   @Override
117   protected ProcessHandler createProcessHandler(final PtyProcess process) {
118     return new PtyProcessHandler(process, getCommand()[0]);
119   }
120
121   @Override
122   protected TtyConnector createTtyConnector(PtyProcess process) {
123     return new PtyProcessTtyConnector(process, myDefaultCharset);
124   }
125
126   @Override
127   public String runningTargetName() {
128     return "Local Terminal";
129   }
130
131   @Override
132   protected String getTerminalConnectionName(PtyProcess process) {
133     return "Local Terminal";
134   }
135
136   public String[] getCommand() {
137     String[] command;
138     String shellPath = TerminalOptionsProvider.getInstance().getShellPath();
139
140     if (SystemInfo.isUnix) {
141       File rcFile = findRCFile();
142
143       String shellName = getShellName(shellPath);
144
145       if (rcFile != null && (shellName.equals("bash") || shellName.equals("sh"))) {
146         command = new String[]{shellPath, "--rcfile", rcFile.getAbsolutePath(), "-i"};
147       }
148       else if (hasLoginArgument(shellName)) {
149         command = new String[]{shellPath, "--login"};
150       }
151       else {
152         command = shellPath.split(" ");
153       }
154     }
155     else {
156       command = new String[]{shellPath};
157     }
158
159     return command;
160   }
161
162   private static class PtyProcessHandler extends ProcessHandler implements TaskExecutor {
163
164     private final PtyProcess myProcess;
165     private final ProcessWaitFor myWaitFor;
166
167     public PtyProcessHandler(PtyProcess process, @NotNull String presentableName) {
168       myProcess = process;
169       myWaitFor = new ProcessWaitFor(process, this, presentableName);
170     }
171
172     @Override
173     public void startNotify() {
174       addProcessListener(new ProcessAdapter() {
175         @Override
176         public void startNotified(ProcessEvent event) {
177           try {
178             myWaitFor.setTerminationCallback(integer -> notifyProcessTerminated(integer));
179           }
180           finally {
181             removeProcessListener(this);
182           }
183         }
184       });
185
186       super.startNotify();
187     }
188
189     @Override
190     protected void destroyProcessImpl() {
191       myProcess.destroy();
192     }
193
194     @Override
195     protected void detachProcessImpl() {
196       destroyProcessImpl();
197     }
198
199     @Override
200     public boolean detachIsDefault() {
201       return false;
202     }
203
204     @Override
205     public boolean isSilentlyDestroyOnClose() {
206       return true;
207     }
208
209     @Nullable
210     @Override
211     public OutputStream getProcessInput() {
212       return myProcess.getOutputStream();
213     }
214
215     @NotNull
216     @Override
217     public Future<?> executeTask(@NotNull Runnable task) {
218       return AppExecutorUtil.getAppExecutorService().submit(task);
219     }
220   }
221 }