Merge branch 'master' of git.labs.intellij.net:idea/community
[idea/community.git] / plugins / hg4idea / src / org / zmlx / hg4idea / command / HgCommandService.java
1 // Copyright 2008-2010 Victor Iacoban
2 //
3 // Licensed under the Apache License, Version 2.0 (the "License");
4 // you may not use this file except in compliance with the License.
5 // You may obtain a copy of the License at
6 //
7 // http://www.apache.org/licenses/LICENSE-2.0
8 //
9 // Unless required by applicable law or agreed to in writing, software distributed under
10 // the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
11 // either express or implied. See the License for the specific language governing permissions and
12 // limitations under the License.
13 package org.zmlx.hg4idea.command;
14
15 import com.intellij.execution.ui.ConsoleViewContentType;
16 import com.intellij.openapi.application.ApplicationManager;
17 import com.intellij.openapi.application.ModalityState;
18 import com.intellij.openapi.components.ServiceManager;
19 import com.intellij.openapi.diagnostic.Logger;
20 import com.intellij.openapi.project.Project;
21 import com.intellij.openapi.vfs.VirtualFile;
22 import com.intellij.vcsUtil.VcsUtil;
23 import org.apache.commons.lang.StringUtils;
24 import org.jetbrains.annotations.Nullable;
25 import org.zmlx.hg4idea.*;
26
27 import javax.swing.*;
28 import java.awt.*;
29 import java.io.DataInputStream;
30 import java.io.DataOutputStream;
31 import java.io.File;
32 import java.io.IOException;
33 import java.net.Socket;
34 import java.nio.charset.Charset;
35 import java.util.Arrays;
36 import java.util.LinkedList;
37 import java.util.List;
38
39 public final class HgCommandService {
40   
41   private static File PROMPT_HOOKS_PLUGIN;
42
43   static final Logger LOG = Logger.getInstance(HgCommandService.class.getName());
44
45   static final List<String> DEFAULT_OPTIONS = Arrays.asList(
46     "--config", "ui.merge=internal:merge"
47   );
48
49   private final Project myProject;
50   private final HgGlobalSettings mySettings;
51   private HgExecutableValidator validator;
52
53   public HgCommandService(Project project, HgGlobalSettings settings) {
54     myProject = project;
55     mySettings = settings;
56     if (PROMPT_HOOKS_PLUGIN == null) {
57       PROMPT_HOOKS_PLUGIN = HgUtil.getTemporaryPythonFile("prompthooks");
58     }
59     validator = new HgExecutableValidator(myProject);
60   }
61
62   public static HgCommandService getInstance(Project project) {
63     return ServiceManager.getService(project, HgCommandService.class);
64   }
65
66   @Nullable
67   HgCommandResult execute(VirtualFile repo, String operation, List<String> arguments) {
68     return execute(
69       repo, DEFAULT_OPTIONS, operation, arguments, Charset.defaultCharset()
70     );
71   }
72
73   @Nullable
74   HgCommandResult execute(VirtualFile repo, List<String> hgOptions,
75     String operation, List<String> arguments) {
76     return execute(repo, hgOptions, operation, arguments, Charset.defaultCharset());
77   }
78
79   @Nullable
80   HgCommandResult execute(VirtualFile repo, List<String> hgOptions,
81     String operation, List<String> arguments, Charset charset) {
82     return execute(repo, hgOptions, operation, arguments, charset, false);
83   }
84
85   HgCommandResult execute(VirtualFile repo, List<String> hgOptions,
86     String operation, List<String> arguments, Charset charset, boolean suppressCommandOutput) {
87
88     if (!validator.check(mySettings)) {
89       return null;
90     }
91
92     List<String> cmdLine = new LinkedList<String>();
93     cmdLine.add(HgVcs.getInstance(myProject).getHgExecutable());
94     if (repo != null) {
95       cmdLine.add("--repository");
96       cmdLine.add(repo.getPath());
97     }
98
99     SocketServer promptServer = new SocketServer(new PromptReceiver());
100     WarningReceiver warningReceiver = new WarningReceiver();
101     SocketServer warningServer = new SocketServer(warningReceiver);
102     if (PROMPT_HOOKS_PLUGIN == null) {
103       throw new RuntimeException("Could not hook into the prompt mechanism of Mercurial");
104     }
105     try {
106       int promptPort = promptServer.start();
107       int warningPort = warningServer.start();
108       cmdLine.add("--config");
109       cmdLine.add("extensions.hg4ideapromptextension=" + PROMPT_HOOKS_PLUGIN.getAbsolutePath());
110       cmdLine.add("--config");
111       cmdLine.add("hg4ideaprompt.port=" + promptPort);
112       cmdLine.add("--config");
113       cmdLine.add("hg4ideawarn.port=" + warningPort);
114
115       // Other parts of the plugin count on the availability of the MQ extension, so make sure it is enabled
116       cmdLine.add("--config");
117       cmdLine.add("extensions.mq=");
118     } catch (IOException e) {
119       showError(e);
120       LOG.error("IOException during preparing command", e);
121       return null;
122     }
123     cmdLine.addAll(hgOptions);
124     cmdLine.add(operation);
125     if (arguments != null && arguments.size() != 0) {
126       cmdLine.addAll(arguments);
127     }
128     ShellCommand shellCommand = new ShellCommand();
129     HgCommandResult result;
130     try {
131       LOG.debug(cmdLine.toString());
132       String workingDir = repo != null ? repo.getPath() : null;
133       result = shellCommand.execute(cmdLine, workingDir, charset);
134     } catch (ShellCommandException e) {
135       showError(e);
136       LOG.error(e.getMessage(), e);
137       return null;
138     } finally {
139       promptServer.stop();
140       warningServer.stop();
141     }
142     String warnings = warningReceiver.getWarnings();
143     result.setWarnings(warnings);
144
145     // logging to the Version Control console (without extensions and configs)
146     final String cmdString = String.format("%s %s %s", HgVcs.HG_EXECUTABLE_FILE_NAME, operation,
147             StringUtils.join(arguments, " "));
148     final HgVcs hgVcs = HgVcs.getInstance(myProject);
149     hgVcs.showMessageInConsole(cmdString, ConsoleViewContentType.USER_INPUT.getAttributes());
150     if (!suppressCommandOutput) {
151       hgVcs.showMessageInConsole(result.getRawOutput(), ConsoleViewContentType.SYSTEM_OUTPUT.getAttributes());
152     }
153     hgVcs.showMessageInConsole(result.getRawError(), ConsoleViewContentType.ERROR_OUTPUT.getAttributes());
154
155     return result;
156
157   }
158
159   private void showError(Exception e) {
160     StringBuilder message = new StringBuilder();
161     message.append(HgVcsMessages.message("hg4idea.command.executable.error",
162       HgVcs.getInstance(myProject).getHgExecutable()))
163       .append("\n")
164       .append("Original Error:\n")
165       .append(e.getMessage());
166
167     VcsUtil.showErrorMessage(
168       myProject,
169       message.toString(),
170       HgVcsMessages.message("hg4idea.error")
171     );
172   }
173
174   private static class WarningReceiver extends SocketServer.Protocol{
175     private StringBuffer warnings = new StringBuffer();
176
177     public boolean handleConnection(Socket socket) throws IOException {
178       DataInputStream dataInput = new DataInputStream(socket.getInputStream());
179
180       int numOfWarnings = dataInput.readInt();
181       for (int i = 0; i < numOfWarnings; i++) {
182         warnings.append(new String(readDataBlock(dataInput)));
183       }
184       return true;
185     }
186
187
188     public String getWarnings() {
189       return warnings.toString();
190     }
191   }
192
193   private static class PromptReceiver extends SocketServer.Protocol {
194
195     public boolean handleConnection(Socket socket) throws IOException {
196       DataInputStream dataInput = new DataInputStream(socket.getInputStream());
197       final String message = new String(readDataBlock(dataInput));
198       int numOfChoices = dataInput.readInt();
199       final Choice[] choices = new Choice[numOfChoices];
200       for (int i = 0; i < numOfChoices; i++) {
201         String choice = new String(readDataBlock(dataInput));
202         choices[i] = new Choice(choice);
203       }
204       int defaultChoiceInt = dataInput.readInt();
205       final Choice defaultChoice = choices[defaultChoiceInt];
206
207       final int[] index = new int[]{-1};
208       ApplicationManager.getApplication().invokeAndWait(new Runnable() {
209         public void run() {
210           Window parent = ApplicationManager.getApplication().getComponent(Window.class);
211           index[0] = JOptionPane.showOptionDialog(
212             parent,
213             message,
214             "hg4idea",
215             JOptionPane.OK_CANCEL_OPTION,
216             JOptionPane.QUESTION_MESSAGE,
217             null,
218             choices,
219             defaultChoice);
220         }
221       }, ModalityState.defaultModalityState());
222
223       int chosen = index[0];
224       DataOutputStream out = new DataOutputStream(socket.getOutputStream());
225       if (chosen == JOptionPane.CLOSED_OPTION) {
226         out.writeInt(-1);
227       } else {
228         out.writeInt(chosen);
229       }
230       return true;
231     }
232
233     private static class Choice{
234       private final String fullString;
235       private final String representation;
236       private final String choiceChar;
237
238       private Choice(String fullString) {
239         this.fullString = fullString;
240         this.representation = fullString.replaceAll("&", "");
241         int index = fullString.indexOf("&");
242         this.choiceChar = "" + fullString.charAt(index + 1);
243         
244       }
245
246       @Override
247       public String toString() {
248         return representation;
249       }
250
251       @Override
252       public boolean equals(Object o) {
253         if (this == o) return true;
254         if (o == null || getClass() != o.getClass()) return false;
255
256         Choice choice = (Choice) o;
257
258         if (!fullString.equals(choice.fullString)) return false;
259
260         return true;
261       }
262
263       @Override
264       public int hashCode() {
265         return fullString.hashCode();
266       }
267     }
268   }
269
270
271 }