X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fcom%2Fdogcows%2FEditor.java;h=564e74ff00cdf7b699fef18a9c66be37e552a74f;hb=d5c3685fc4d454fee9b42436c56c2fd18b17a029;hp=ddb7ccfe69043010dac3787b77a3bb15297efc4d;hpb=70fd7817176222991ace7906ecfd633c9b3eb272;p=chaz%2Fvimcoder diff --git a/src/com/dogcows/Editor.java b/src/com/dogcows/Editor.java index ddb7ccf..564e74f 100644 --- a/src/com/dogcows/Editor.java +++ b/src/com/dogcows/Editor.java @@ -17,16 +17,37 @@ import com.topcoder.shared.problem.TestCase; /** * @author Charles McGarvey - * + * The TopCoder Arena editor plug-in providing support for Vim. + * + * Distributable under the terms and conditions of the 2-clause BSD license; + * see the file COPYING for a complete text of the license. */ public class Editor { + /** + * The problem ID number. + */ private String id; + + /** + * The name of the class. + */ private String name; + + /** + * The path of the current source file. + */ private File sourceFile; + + /** + * The path of the problem directory. + */ private File directory; + /** + * Map languages names to file extensions. + */ private static final Map languageExtension = new HashMap(); static { @@ -38,19 +59,28 @@ public class Editor } + /** + * Construct an editor with the problem objects given us by the Arena. + * @param component A container for the particulars of the problem. + * @param language The currently selected language. + * @param renderer A helper object to help format the problem statement. + * @throws Exception If the editor could not set itself up. + */ public Editor(ProblemComponentModel component, Language language, - Renderer renderer) throws IOException + Renderer renderer) throws Exception { this.id = String.valueOf(component.getProblem().getProblemID()); this.name = component.getClassName(); - File topDir = new File(System.getProperty("user.home"), ".vimcoder"); + // Make sure the top-level vimcoder directory exists. + File topDir = VimCoder.getStorageDirectory(); if (!topDir.isDirectory()) { if (!topDir.mkdirs()) throw new IOException(topDir.getPath()); } + // Make sure the problem directory exists. this.directory = new File(topDir, id); if (!directory.isDirectory()) { @@ -60,20 +90,22 @@ public class Editor String lang = language.getName(); String ext = languageExtension.get(lang); + // Set up the terms used for the template expansion. HashMap terms = new HashMap(); - terms.put("RETURNTYPE", component.getReturnType().getDescriptor(language).replaceAll("\\s+", "")); + terms.put("RETURNTYPE",component.getReturnType().getDescriptor(language).replaceAll("\\s+", "")); terms.put("CLASSNAME", name); terms.put("METHODNAME", component.getMethodName()); terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), component.getParamNames(), language)); - terms.put("METHODPARAMNAMES", Utility.join(component.getParamNames(), ", ")); - terms.put("METHODPARAMSTREAMIN", Utility.join(component.getParamNames(), " >> ")); - terms.put("METHODPARAMSTREAMOUT", Utility.join(component.getParamNames(), " << ")); + terms.put("METHODPARAMNAMES", Util.join(component.getParamNames(), ", ")); + terms.put("METHODPARAMSTREAMIN", Util.join(component.getParamNames(), " >> ")); + terms.put("METHODPARAMSTREAMOUT", Util.join(component.getParamNames(), " << ")); terms.put("METHODPARAMDECLARES", getMethodParamDeclarations(component.getParamTypes(), component.getParamNames(), language)); + // Write the problem statement as an HTML file in the problem directory. File problemFile = new File(directory, "Problem.html"); if (!problemFile.canRead()) { @@ -82,22 +114,37 @@ public class Editor { writer.write(renderer.toHTML(language)); } - catch (Exception exception) + finally { + writer.close(); } - writer.close(); } + // Expand the template for the main class and write it to the current + // source file. sourceFile = new File(directory, name + "." + ext); if (!sourceFile.canRead()) { - String text = Utility.expandTemplate(Utility.readResource(lang + "Template"), - terms); + String text = Util.expandTemplate(Util.readResource(lang + "Template"), + terms); FileWriter writer = new FileWriter(sourceFile); writer.write(text); writer.close(); } + + // Expand the driver template and write it to a source file. + File driverFile = new File(directory, "driver." + ext); + if (!driverFile.canRead()) + { + String text = Util.expandTemplate(Util.readResource(lang + "Driver"), + terms); + FileWriter writer = new FileWriter(driverFile); + writer.write(text); + writer.close(); + } + // Write the test cases to a text file. The driver code can read this + // file and perform the tests based on what it reads. File testcaseFile = new File(directory, "testcases.txt"); if (!testcaseFile.canRead()) { @@ -118,19 +165,10 @@ public class Editor writer.close(); } - File driverFile = new File(directory, "driver." + ext); - if (!driverFile.canRead()) - { - String text = Utility.expandTemplate(Utility.readResource(lang + "Driver"), - terms); - FileWriter writer = new FileWriter(driverFile); - writer.write(text); - writer.close(); - } - + // Finally, expand the Makefile template and write it. File makeFile = new File(directory, "Makefile"); { - String text = Utility.expandTemplate(Utility.readResource(lang + "Makefile"), + String text = Util.expandTemplate(Util.readResource(lang + "Makefile"), terms); FileWriter writer = new FileWriter(makeFile); writer.write(text); @@ -138,36 +176,77 @@ public class Editor } } + /** + * Save the source code provided by the server, and tell the Vim server to + * edit the current source file. + * @param source The source code. + * @throws Exception If the source couldn't be written or the Vim server + * had a problem. + */ public void setSource(String source) throws Exception { FileWriter writer = new FileWriter(new File(directory, name)); writer.write(source); writer.close(); - doVimCommand("--remote-tab-silent", sourceFile.getPath()); + sendVimCommand("--remote-tab-silent", sourceFile.getPath()); } + /** + * Read the source code from the current source file. + * @return The source code. + * @throws IOException If the source file could not be read. + */ public String getSource() throws IOException { - return Utility.readFile(sourceFile) + "\n// Edited by " + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; + return Util.readFile(sourceFile) + "\n// Edited by " + + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; } - private void doVimCommand(String command, String argument) throws Exception + /** + * Send a command to the Vim server. If the server isn't running, it will + * be started with the name VIMCODER#### where #### is the problem ID. + * @param command The command to send to the server. + * @param argument A single argument for the remote command. + * @throws Exception If the command could not be sent. + */ + private void sendVimCommand(String command, + String argument) throws Exception { String[] arguments = {argument}; - doVimCommand(command, arguments); + sendVimCommand(command, arguments); } - private void doVimCommand(String command, String[] arguments) throws Exception + /** + * Send a command to the Vim server. If the server isn't running, it will + * be started with the name VIMCODER#### where #### is the problem ID. + * @param command The command to send to the server. + * @param argument Arguments for the remote command. + * @throws Exception If the command could not be sent. + */ + private void sendVimCommand(String command, + String[] arguments) throws Exception { - String[] exec = {"gvim", "--servername", "VimCoder" + id, - command}; - exec = Utility.concat(exec, arguments); - Process child = Runtime.getRuntime().exec(exec, null, directory); + String[] vimCommand = VimCoder.getVimCommand().split("\\s"); + String[] flags = {"--servername", "VimCoder" + id, command}; + vimCommand = Util.concat(vimCommand, flags); + vimCommand = Util.concat(vimCommand, arguments); + Process child = Runtime.getRuntime().exec(vimCommand, null, directory); - long expire = System.currentTimeMillis() + 500; + /* FIXME: This is a hack with a magic number. The problem is the Vim + * process doesn't fork to the background on some systems, so we can't + * wait on the child. At the same time, calling this method before the + * previous child could finish initializing the server may result in + * multiple editor windows popping up. We'd also like to be able to + * get the return code from the child if we can. The workaround here is + * to stall the thread for a little while or until we know the child + * does exit. If the child never exits before the timeout, we will + * assume it is not backgrounding and that everything worked. This all + * works very well in practice, but perhaps there's a better way... */ + long expire = System.currentTimeMillis() + 250; while (System.currentTimeMillis() < expire) { + Thread.yield(); try { int exitCode = child.exitValue(); @@ -176,38 +255,61 @@ public class Editor } catch (IllegalThreadStateException exception) { + // The child has not exited; intentionally ignoring exception. } - Thread.yield(); } } + /** + * Convert an array of data types to an array of strings according to a + * given language. + * @param types The data types. + * @param language The language to use in the conversion. + * @return The array of string representations of the data types. + */ + private String[] getStringTypes(DataType[] types, Language language) + { + String[] strings = new String[types.length]; + for (int i = 0; i < types.length; ++i) + { + strings[i] = types[i].getDescriptor(language).replaceAll("\\s+", ""); + } + return strings; + } + + /** + * Combine the data types and parameter names into a comma-separated list of + * the method parameters. The result could be used inside the parentheses + * of a method declaration. + * @param types The data types of the parameters. + * @param names The names of the parameters. + * @param language The language used for representing the data types. + * @return The list of parameters. + */ private String getMethodParams(DataType[] types, String[] names, Language language) { - StringBuilder text = new StringBuilder(); - - text.append(types[0].getDescriptor(language).replaceAll("\\s+", "") + " " + names[0]); - for (int i = 1; i < names.length; ++i) - { - text.append(", " + types[i].getDescriptor(language).replaceAll("\\s+", "") + " " + names[i]); - } - - return text.toString(); + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, " "), ", "); } - private String getMethodParamDeclarations (DataType[] types, + /** + * Combine the data types and parameter names into a group of variable + * declarations. Each declaration is separated by a new line and terminated + * with a semicolon. + * @param types The data types of the parameters. + * @param names The names of the parameters. + * @param language The language used for representing the data types. + * @return The parameters as a block of declarations. + */ + private String getMethodParamDeclarations(DataType[] types, String[] names, Language language) { - StringBuilder text = new StringBuilder(); - - for (int i = 0; i < names.length; ++i) - { - text.append(types[i].getDescriptor(language).replaceAll("\\s+", "") + "\t" + names[i] + ";" + System.getProperty("line.separator")); - } - - return text.toString(); + final String end = ";" + System.getProperty("line.separator"); + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, "\t"), end) + end; } }