X-Git-Url: https://git.dogcows.com/gitweb?a=blobdiff_plain;f=src%2Fcom%2Fdogcows%2FEditor.java;h=15dad35a8f5712c70c21017377e30e73dbea181f;hb=5513994bbc39ef7c7ca1917e2d07d8c53b30ea8d;hp=dda5481fe2971f2173044d0758b8fdf887900adc;hpb=bde33c9ffb21a7a0fa71e73a106154c451d48ad2;p=chaz%2Fvimcoder diff --git a/src/com/dogcows/Editor.java b/src/com/dogcows/Editor.java index dda5481..15dad35 100644 --- a/src/com/dogcows/Editor.java +++ b/src/com/dogcows/Editor.java @@ -7,8 +7,7 @@ import java.io.FileReader; import java.io.FileWriter; import java.io.IOException; import java.io.InputStream; -import java.util.HashMap; -import java.util.Map; +import java.util.*; import com.topcoder.client.contestant.ProblemComponentModel; import com.topcoder.shared.language.Language; @@ -18,17 +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 { @@ -40,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(); + 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()) { @@ -62,15 +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)); - terms.put("CLASSNAME", component.getClassName()); + 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", Utilities.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()) { @@ -79,104 +114,223 @@ public class Editor { writer.write(renderer.toHTML(language)); } - catch (Exception exception) + finally { + writer.close(); } - writer.close(); } - sourceFile = new File(directory, terms.get("CLASSNAME") + "." + ext); + // 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 = Utilities.expandTemplate(Utilities.readResource(lang + "Template"), - terms); + String text = Util.expandTemplate(readTemplate(lang + "Template"), + terms); FileWriter writer = new FileWriter(sourceFile); writer.write(text); writer.close(); } - - File driverFile = new File(directory, "driver" + "." + ext); + + // Expand the driver template and write it to a source file. + File driverFile = new File(directory, "driver." + ext); if (!driverFile.canRead()) { - StringBuilder testCases = new StringBuilder(); + String text = Util.expandTemplate(readTemplate(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()) + { + StringBuilder text = new StringBuilder(); if (component.hasTestCases()) { - HashMap testTerms = new HashMap(); - testTerms.putAll(terms); - String template = Utilities.readResource(lang + "Test"); for (TestCase testCase : component.getTestCases()) { - testTerms.put("TESTOUTPUT", "\"" + Utilities.quote(testCase.getOutput()) + "\""); - testTerms.put("TESTINPUTS", Utilities.join(testCase.getInput(), ", ")); - testCases.append(Utilities.expandTemplate(template, testTerms)); + text.append(testCase.getOutput() + System.getProperty("line.separator")); + for (String input : testCase.getInput()) + { + text.append(input + System.getProperty("line.separator")); + } } } - terms.put("TESTCASES", testCases.toString()); - - String text = Utilities.expandTemplate(Utilities.readResource(lang + "Driver"), - terms); - FileWriter writer = new FileWriter(driverFile); - writer.write(text); + FileWriter writer = new FileWriter(testcaseFile); + writer.write(text.toString()); writer.close(); } + // Finally, expand the Makefile template and write it. File makeFile = new File(directory, "Makefile"); { - String text = Utilities.expandTemplate(Utilities.readResource(lang + "Makefile"), - terms); + String text = Util.expandTemplate(readTemplate(lang + "Makefile"), + terms); FileWriter writer = new FileWriter(makeFile); writer.write(text); writer.close(); } } - - public void setSource(String source) throws IOException + + /** + * 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 Utilities.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 boolean doVimCommand(String command, String argument) + /** + * 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}; - return doVimCommand(command, arguments); + sendVimCommand(command, arguments); + } + + /** + * 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[] 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); + + /* 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. */ + long expire = System.currentTimeMillis() + 250; + while (System.currentTimeMillis() < expire) + { + Thread.yield(); + try + { + int exitCode = child.exitValue(); + if (exitCode != 0) throw new Exception("Vim process returned exit code " + exitCode + "."); + break; + } + catch (IllegalThreadStateException exception) + { + // The child has not exited; intentionally ignoring exception. + } + } } - private boolean doVimCommand(String command, String[] arguments) + + /** + * Read a template. We first look in the storage directory. If we can't + * find one, we look among the resources. + * @param tName The name of the template. + * @return The contents of the template file, or an empty string. + */ + private String readTemplate(String tName) { + File templateFile = new File(VimCoder.getStorageDirectory(), tName); try { - String[] exec = {"gvim", "--servername", "VimCoder" + id, - command}; - exec = Utilities.concat(exec, arguments); - Runtime.getRuntime().exec(exec, null, directory); + if (templateFile.canRead()) return Util.readFile(templateFile); + return Util.readResource(tName); } catch (IOException exception) { - System.out.println("Failed to launch external vim process. :-("); + return ""; } - return false; } - private String getMethodParams(DataType[] types, String[] names, Language language) + + /** + * 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) { - StringBuilder text = new StringBuilder(); - - text.append(types[0].getDescriptor(language) + " " + names[0]); - for (int i = 1; i < names.length; ++i) + String[] strings = new String[types.length]; + for (int i = 0; i < types.length; ++i) { - text.append(", " + types[i].getDescriptor(language) + " " + names[i]); + strings[i] = types[i].getDescriptor(language).replaceAll("\\s+", ""); } - - return text.toString(); + 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) + { + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, " "), ", "); + } + + /** + * 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) + { + final String end = ";" + System.getProperty("line.separator"); + String[] typeStrings = getStringTypes(types, language); + return Util.join(Util.combine(typeStrings, names, "\t"), end) + end; } }