X-Git-Url: https://git.dogcows.com/gitweb?p=chaz%2Fvimcoder;a=blobdiff_plain;f=src%2Fcom%2Fdogcows%2FEditor.java;h=87403240fa43c7de09fcd36b3035729dabadb312;hp=dda5481fe2971f2173044d0758b8fdf887900adc;hb=482e4985ec29af24fd5dfaae71a107444641287f;hpb=bde33c9ffb21a7a0fa71e73a106154c451d48ad2 diff --git a/src/com/dogcows/Editor.java b/src/com/dogcows/Editor.java index dda5481..8740324 100644 --- a/src/com/dogcows/Editor.java +++ b/src/com/dogcows/Editor.java @@ -1,14 +1,10 @@ package com.dogcows; -import java.io.BufferedReader; import java.io.File; -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,165 +14,344 @@ 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 { - private String id; - private String name; - - private File sourceFile; - private File directory; - - - private static final Map languageExtension = new HashMap(); - static - { - languageExtension.put("Java", "java"); - languageExtension.put("C++", "cc"); - languageExtension.put("C#", "cs"); - languageExtension.put("VB", "vb"); - languageExtension.put("Python", "py"); - } - - - public Editor(ProblemComponentModel component, - Language language, - Renderer renderer) throws IOException - { - this.id = String.valueOf(component.getProblem().getProblemID()); - this.name = component.getClassName(); - - File topDir = new File(System.getProperty("user.home"), ".vimcoder"); - if (!topDir.isDirectory()) - { - if (!topDir.mkdirs()) throw new IOException(topDir.getPath()); - } - - this.directory = new File(topDir, id); - if (!directory.isDirectory()) - { - if (!directory.mkdirs()) throw new IOException(directory.getPath()); - } - - String lang = language.getName(); - String ext = languageExtension.get(lang); - - HashMap terms = new HashMap(); - terms.put("RETURNTYPE", component.getReturnType().getDescriptor(language)); - terms.put("CLASSNAME", component.getClassName()); - terms.put("METHODNAME", component.getMethodName()); - terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), - component.getParamNames(), - language)); - terms.put("METHODPARAMNAMES", Utilities.join(component.getParamNames(), ", ")); - - File problemFile = new File(directory, "Problem.html"); - if (!problemFile.canRead()) - { - FileWriter writer = new FileWriter(problemFile); - try - { - writer.write(renderer.toHTML(language)); - } - catch (Exception exception) - { - } - writer.close(); - } - - sourceFile = new File(directory, terms.get("CLASSNAME") + "." + ext); - if (!sourceFile.canRead()) - { - String text = Utilities.expandTemplate(Utilities.readResource(lang + "Template"), - terms); - FileWriter writer = new FileWriter(sourceFile); - writer.write(text); - writer.close(); - } - - File driverFile = new File(directory, "driver" + "." + ext); - if (!driverFile.canRead()) - { - StringBuilder testCases = 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)); - } - } - terms.put("TESTCASES", testCases.toString()); - - String text = Utilities.expandTemplate(Utilities.readResource(lang + "Driver"), - terms); - FileWriter writer = new FileWriter(driverFile); - writer.write(text); - writer.close(); - } - - File makeFile = new File(directory, "Makefile"); - { - String text = Utilities.expandTemplate(Utilities.readResource(lang + "Makefile"), - terms); - FileWriter writer = new FileWriter(makeFile); - writer.write(text); - writer.close(); - } - } - - public void setSource(String source) throws IOException - { - FileWriter writer = new FileWriter(new File(directory, name)); - writer.write(source); - writer.close(); - doVimCommand("--remote-tab-silent", sourceFile.getPath()); - } - - public String getSource() throws IOException - { - return Utilities.readFile(sourceFile) + "\n// Edited by " + VimCoder.version + "\n// " + VimCoder.website + "\n\n"; - } - - - private boolean doVimCommand(String command, String argument) - { - String[] arguments = {argument}; - return doVimCommand(command, arguments); - } - - private boolean doVimCommand(String command, String[] arguments) - { - try - { - String[] exec = {"gvim", "--servername", "VimCoder" + id, - command}; - exec = Utilities.concat(exec, arguments); - Runtime.getRuntime().exec(exec, null, directory); - } - catch (IOException exception) - { - System.out.println("Failed to launch external vim process. :-("); - } - return false; - } - - private String getMethodParams(DataType[] types, String[] names, Language language) - { - StringBuilder text = new StringBuilder(); - - text.append(types[0].getDescriptor(language) + " " + names[0]); - for (int i = 1; i < names.length; ++i) - { - text.append(", " + types[i].getDescriptor(language) + " " + names[i]); - } - - return text.toString(); - } + /** + * The problem ID number. + */ + private String id; + + /** + * The name of the class. + */ + private String name; + + /** + * The name of the contest. + */ + private String contestName; + + /** + * The point value. + */ + private String points; + + /** + * 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 + { + languageExtension.put("Java", "java"); + languageExtension.put("C++", "cc"); + languageExtension.put("C#", "cs"); + languageExtension.put("VB", "vb"); + languageExtension.put("Python", "py"); + } + + + /** + * 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 Exception + { + this.id = String.valueOf(component.getProblem().getProblemID()); + this.name = component.getClassName(); + this.contestName = component.getProblem().getRound().getContestName().replaceAll(" ", "-"); + this.points = String.valueOf(component.getPoints().intValue()); + + // 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. + File newStyleDirectory = new File(new File(topDir, contestName), points); + File oldStyleDirectory = new File(topDir, id); + if (newStyleDirectory.isDirectory()) + { + this.directory = newStyleDirectory; + } + else if (oldStyleDirectory.isDirectory()) + { + this.directory = oldStyleDirectory; + } + else if (VimCoder.isContestDirNames()) + { + this.directory = newStyleDirectory; + if (!directory.mkdirs()) throw new IOException(directory.getPath()); + } + else + { + this.directory = oldStyleDirectory; + if (!directory.mkdirs()) throw new IOException(directory.getPath()); + } + + 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", name); + terms.put("METHODNAME", component.getMethodName()); + terms.put("METHODPARAMS", getMethodParams(component.getParamTypes(), + component.getParamNames(), language)); + 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)); + terms.put("VIMCODER", VimCoder.version); + + // Write the problem statement as an HTML file in the problem directory. + File problemFile = new File(directory, "Problem.html"); + if (!problemFile.canRead()) + { + FileWriter writer = new FileWriter(problemFile); + try + { + writer.write(renderer.toHTML(language)); + } + finally + { + writer.close(); + } + } + + // Expand the template for the main class and write it to the current + // source file. + this.sourceFile = new File(directory, name + "." + ext); + if (!sourceFile.canRead()) + { + String text = Util.expandTemplate(readTemplate(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(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()) + { + for (TestCase testCase : component.getTestCases()) + { + text.append(testCase.getOutput() + System.getProperty("line.separator")); + for (String input : testCase.getInput()) + { + text.append(input + System.getProperty("line.separator")); + } + } + } + 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"); + if (!makeFile.canRead()) + { + String text = Util.expandTemplate(readTemplate(lang + "Makefile"), terms); + FileWriter writer = new FileWriter(makeFile); + writer.write(text); + writer.close(); + } + } + + /** + * 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(); + 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 Util.readFile(sourceFile); + } + + + /** + * 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}; + 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 pretty bad hack. The problem is that 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 see that the child exits. If the child never exits + * before the timeout, we will assume it is not backgrounding and + * that everything worked. This works as long as the Vim server is + * able to start within the stall period. */ + long expire = System.currentTimeMillis() + 2500; + 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. + } + } + } + + + /** + * 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 + { + if (templateFile.canRead()) return Util.readFile(templateFile); + return Util.readResource(tName); + } + catch (IOException exception) + { + return ""; + } + } + + + /** + * 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); + } + 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; + } } +// vim:noet:ts=8