Add possibility to customize terminal environment variables
[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.concurrency.AppExecutorUtil;
31 import com.intellij.util.containers.HashMap;
32 import com.jediterm.pty.PtyProcessTtyConnector;
33 import com.jediterm.terminal.TtyConnector;
34 import com.pty4j.PtyProcess;
35 import com.pty4j.util.PtyUtil;
36 import org.jetbrains.annotations.NotNull;
37 import org.jetbrains.annotations.Nullable;
38
39 import java.io.File;
40 import java.io.IOException;
41 import java.io.OutputStream;
42 import java.nio.charset.Charset;
43 import java.util.Map;
44 import java.util.concurrent.ExecutionException;
45 import java.util.concurrent.Future;
46
47 /**
48  * @author traff
49  */
50 public class LocalTerminalDirectRunner extends AbstractTerminalRunner<PtyProcess> {
51   private static final Logger LOG = Logger.getInstance(LocalTerminalDirectRunner.class);
52
53   private final Charset myDefaultCharset;
54
55   public LocalTerminalDirectRunner(Project project) {
56     super(project);
57     myDefaultCharset = CharsetToolkit.UTF8_CHARSET;
58   }
59
60   private static boolean hasLoginArgument(String name) {
61     return name.equals("bash") || name.equals("sh") || name.equals("zsh");
62   }
63
64   private static String getShellName(String path) {
65     return new File(path).getName();
66   }
67
68   private static File findRCFile() {
69     try {
70       final String folder = PtyUtil.getPtyLibFolderPath();
71       if (folder != null) {
72         File rcFile = new File(folder, "jediterm.in");
73         if (rcFile.exists()) {
74           return rcFile;
75         }
76       }
77     }
78     catch (Exception e) {
79       LOG.warn("Unable to get JAR folder", e);
80     }
81     return null;
82   }
83
84   @NotNull
85   public static LocalTerminalDirectRunner createTerminalRunner(Project project) {
86     return new LocalTerminalDirectRunner(project);
87   }
88
89   @Override
90   protected PtyProcess createProcess(@Nullable String directory) throws ExecutionException {
91     Map<String, String> envs = new HashMap<String, String>(System.getenv());
92     if (!SystemInfo.isWindows) {
93       envs.put("TERM", "xterm-256color");
94     }
95     EncodingEnvironmentUtil.setLocaleEnvironmentIfMac(envs, myDefaultCharset);
96     
97     for (LocalTerminalCustomizer customizer: LocalTerminalCustomizer.EP_NAME.getExtensions()) {
98       customizer.setupEnvironment(envs);
99     }
100     
101     try {
102       return PtyProcess.exec(getCommand(), envs, directory != null ? directory : currentProjectFolder());
103     }
104     catch (IOException e) {
105       throw new ExecutionException(e);
106     }
107   }
108
109   private String currentProjectFolder() {
110     final ProjectRootManager projectRootManager = ProjectRootManager.getInstance(myProject);
111
112     final VirtualFile[] roots = projectRootManager.getContentRoots();
113     if (roots.length == 1) {
114       roots[0].getCanonicalPath();
115     }
116     final VirtualFile baseDir = myProject.getBaseDir();
117     return baseDir == null ? null : baseDir.getCanonicalPath();
118   }
119
120   @Override
121   protected ProcessHandler createProcessHandler(final PtyProcess process) {
122     return new PtyProcessHandler(process, getCommand()[0]);
123   }
124
125   @Override
126   protected TtyConnector createTtyConnector(PtyProcess process) {
127     return new PtyProcessTtyConnector(process, myDefaultCharset);
128   }
129
130   @Override
131   public String runningTargetName() {
132     return "Local Terminal";
133   }
134
135   @Override
136   protected String getTerminalConnectionName(PtyProcess process) {
137     return "Local Terminal";
138   }
139
140   public String[] getCommand() {
141     String[] command;
142     String shellPath = TerminalOptionsProvider.getInstance().getShellPath();
143
144     if (SystemInfo.isUnix) {
145       File rcFile = findRCFile();
146
147       String shellName = getShellName(shellPath);
148
149       if (rcFile != null && (shellName.equals("bash") || shellName.equals("sh"))) {
150         command = new String[]{shellPath, "--rcfile", rcFile.getAbsolutePath(), "-i"};
151       }
152       else if (hasLoginArgument(shellName)) {
153         command = new String[]{shellPath, "--login"};
154       }
155       else {
156         command = shellPath.split(" ");
157       }
158     }
159     else {
160       command = new String[]{shellPath};
161     }
162
163     return command;
164   }
165
166   private static class PtyProcessHandler extends ProcessHandler implements TaskExecutor {
167
168     private final PtyProcess myProcess;
169     private final ProcessWaitFor myWaitFor;
170
171     public PtyProcessHandler(PtyProcess process, @NotNull String presentableName) {
172       myProcess = process;
173       myWaitFor = new ProcessWaitFor(process, this, presentableName);
174     }
175
176     @Override
177     public void startNotify() {
178       addProcessListener(new ProcessAdapter() {
179         @Override
180         public void startNotified(ProcessEvent event) {
181           try {
182             myWaitFor.setTerminationCallback(integer -> notifyProcessTerminated(integer));
183           }
184           finally {
185             removeProcessListener(this);
186           }
187         }
188       });
189
190       super.startNotify();
191     }
192
193     @Override
194     protected void destroyProcessImpl() {
195       myProcess.destroy();
196     }
197
198     @Override
199     protected void detachProcessImpl() {
200       destroyProcessImpl();
201     }
202
203     @Override
204     public boolean detachIsDefault() {
205       return false;
206     }
207
208     @Override
209     public boolean isSilentlyDestroyOnClose() {
210       return true;
211     }
212
213     @Nullable
214     @Override
215     public OutputStream getProcessInput() {
216       return myProcess.getOutputStream();
217     }
218
219     @NotNull
220     @Override
221     public Future<?> executeTask(@NotNull Runnable task) {
222       return AppExecutorUtil.getAppExecutorService().submit(task);
223     }
224   }
225 }