From: Charles McGarvey Date: Mon, 8 Nov 2010 05:17:45 +0000 (-0700) Subject: initial commit X-Git-Tag: v0.1^0 X-Git-Url: https://git.dogcows.com/gitweb?a=commitdiff_plain;h=8a2b2bbbe6e13cee2f5118e1ce814bf17f058299;p=chaz%2Fvimcoder initial commit --- 8a2b2bbbe6e13cee2f5118e1ce814bf17f058299 diff --git a/.classpath b/.classpath new file mode 100644 index 0000000..fc6d16a --- /dev/null +++ b/.classpath @@ -0,0 +1,11 @@ + + + + + + + + + + + diff --git a/.project b/.project new file mode 100644 index 0000000..2880acc --- /dev/null +++ b/.project @@ -0,0 +1,17 @@ + + + VimCoder + + + + + + org.eclipse.jdt.core.javabuilder + + + + + + org.eclipse.jdt.core.javanature + + diff --git a/COPYING b/COPYING new file mode 100644 index 0000000..0b94026 --- /dev/null +++ b/COPYING @@ -0,0 +1,27 @@ + +The Simplified BSD License + +Copyright (c) 2010, Charles McGarvey +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +1. Redistributions of source code must retain the above copyright notice, + this list of conditions and the following disclaimer. +2. Redistributions in binary form must reproduce the above copyright + notice, this list of conditions and the following disclaimer in the + documentation and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE +ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE +LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS +INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN +CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) +ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE +POSSIBILITY OF SUCH DAMAGE. + diff --git a/README b/README new file mode 100644 index 0000000..f7a4828 --- /dev/null +++ b/README @@ -0,0 +1,68 @@ + +VimCoder +-------- + +The TopCoder Arena editor plug-in providing support for Vim. + +The version is very experimental, and I haven't gotten around to writing +any substantial documentation. It is also not very configurable without +changing the code. All of these problems will be remedied eventually. + + +Features: + +- Use Vim to edit your TopCoder Arena problems! +- Keeps track of code files locally and syncs with the server, so no more + copy and pasting to and from the Arena applet. +- Works with any language. +- Downloads and stores a copy of the problem statement with your code for + off-line viewing. +- Basic support for templates (currently C++ only). +- Local test-case drivers can be automatically created with the example + test-case data (currently C++ only, and has bugs). + + +Compatibility: + +I've tested the plug-in only on my own computer which runs x86_64 Linux. +It should work on other setups, but note the following: + +- The program "gvim" should be in your PATH. +- Vim needs to be compiled with client/server support. To check, make sure + the command "vim --version | grep +clientserver" prints something. +- The test-case driver generated for C++ uses POSIX functions for timing, + so it may not compile on non-POSIX systems (i.e. Windows). The C++Driver + template simply needs to be changed to support other timing functions. + + +How To Install: + +Assuming you have the downloaded the VimCoder jar file: + +- Run the TopCoder Arena applet. +- Click the "Options" menu and select "Editor" to show the editor + preferences. +- Click the "Add" button to bring up a new window. +- For "Name," type "Vim" or whatever you want to represent this plug-in. +- For "EntryPoint," type "com.dogcows.VimCoder" without the quotes. +- For "ClassPath," click on "Browse" and locate the VimCoder jar file. The + third field should now have the path to the jar file. +- Click "OK" to close the window with the three fields. +- Click "Save." + +You should now be able select "Vim" (or whatever you entered the first +field) to use the plug-in. If it doesn't work (i.e. nothing happens), you +may need to restart the applet or set the plug-in as the default editor. +Your mileage may vary. + + +Known Bugs: + +- Lack of documentation. The code itself is also poorly documented. +- Only C++ templates are provided. +- The generated C++ driver code won't compile when the method has one or + more parameters that are vectors (i.e. vectors of strings). +- The $CARAT$ token doesn't seem to work in the templates. +- Directory where code and problem data is saved is hardcoded at + ~/.vimcoder. This should be configurable. + diff --git a/src/com/dogcows/VimCoder.java b/src/com/dogcows/VimCoder.java new file mode 100644 index 0000000..489e789 --- /dev/null +++ b/src/com/dogcows/VimCoder.java @@ -0,0 +1,403 @@ + +package com.dogcows; + +import javax.swing.*; +import java.awt.*; +import java.io.*; +import java.text.SimpleDateFormat; +import java.util.*; +import com.topcoder.client.contestant.ProblemComponentModel; +import com.topcoder.shared.language.*; +import com.topcoder.shared.problem.*; +import com.topcoder.shared.problem.Renderer; + +/** + * @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 VimCoder +{ + /** + * + */ + private final static String version = "VimCoder 0.1"; + private final static String website = "http://www.dogcows.com/vimcoder"; + + private JPanel panel; + private JTextArea logArea; + + private Editor editor; + + + 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"); + } + + + private class Editor + { + private String id; + private String name; + + private File sourceFile; + private File directory; + + + 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, String.valueOf(component.getProblem().getProblemID())); + 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", 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 = expandTemplate(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 = readResource(lang + "Test"); + for (TestCase testCase : component.getTestCases()) + { + testTerms.put("TESTOUTPUT", "\"" + quote(testCase.getOutput()) + "\""); + testTerms.put("TESTINPUTS", join(testCase.getInput(), ", ")); + testCases.append(expandTemplate(template, testTerms)); + } + } + terms.put("TESTCASES", testCases.toString()); + + String text = expandTemplate(readResource(lang + "Driver"), terms); + FileWriter writer = new FileWriter(driverFile); + writer.write(text); + writer.close(); + } + + File makeFile = new File(directory, "Makefile"); + { + String text = expandTemplate(readResource(lang + "Makefile"), terms); + FileWriter writer = new FileWriter(makeFile); + writer.write(text); + writer.close(); + } + } + + public void setSource(String source) throws IOException + { + String actualSource = readFile(sourceFile); + if (!actualSource.equals(source)) + { + File actualFile = new File(directory, name); + FileWriter writer = new FileWriter(actualFile); + writer.write(source); + writer.close(); + } + doVimCommand("--remote-tab-silent", sourceFile.getPath()); + doVimCommand("--remote-send", ":if search('\\$CARAT\\\\$') != 0normal df$endif:redraw"); + } + + public String getSource() throws IOException + { + return readFile(sourceFile) + "\n// Edited by " + version + "\n// " + website + "\n\n"; + } + + public void setTextEnabled(boolean enable) + { + doVimCommand("--remote-send", ":set readonly:echo \"The contest is over.\""); + } + + + 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 = concat(exec, arguments); + + Process child = Runtime.getRuntime().exec(exec); + if (child.waitFor() == 0) + { + return true; + } + else + { + logError("vim command failed"); + } + } + catch (IOException exception) + { + logError("failed to launch external vim process"); + return false; + } + catch (InterruptedException exception) + { + logWarning("interrupted while waiting on 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(); + } + + private String readFile(File file) throws IOException + { + StringBuilder text = new StringBuilder(); + + BufferedReader reader = new BufferedReader(new FileReader(file.getPath())); + try + { + String line = null; + + while ((line = reader.readLine()) != null) + { + text.append(line + System.getProperty("line.separator")); + } + } + finally + { + reader.close(); + } + + return text.toString(); + } + + private String readResource(String path) throws IOException + { + StringBuilder text = new StringBuilder(); + + InputStream stream = getClass().getResourceAsStream("resources/" + path); + if (stream != null) + { + try + { + byte[] buffer = new byte[4096]; + int numBytes = 0; + while (0 < (numBytes = stream.read(buffer))) text.append(new String(buffer, 0, numBytes)); + } + finally + { + stream.close(); + } + } + + return text.toString(); + } + + private String expandTemplate(String template, Map terms) + { + String text = template; + for (String key : terms.keySet()) + { + text = text.replaceAll("\\$" + key + "\\$", quote(terms.get(key))); + } + return text; + } + } + + + public static T[] concat(T[] a, T[] b) + { + T[] result = Arrays.copyOf(a, a.length + b.length); + System.arraycopy(b, 0, result, a.length, b.length); + return result; + } + + public static String join(String[] a, String glue) + { + if (a.length == 0) return ""; + StringBuilder result = new StringBuilder(); + result.append(a[0]); + for (int i = 1; i < a.length; ++i) result.append(glue).append(a[i]); + return result.toString(); + } + + public static String quote(String a) + { + a = a.replaceAll("\\\\", "\\\\\\\\"); + a = a.replaceAll("\"", "\\\\\\\""); + return a; + } + + + public VimCoder() + { + logArea = new JTextArea(); + logArea.setForeground(Color.GREEN); + logArea.setBackground(Color.BLACK); + logArea.setEditable(false); + Font font = new Font("Courier", Font.PLAIN, 12); + if (font != null) logArea.setFont(font); + + panel = new JPanel(new BorderLayout()); + panel.add(new JScrollPane(logArea), BorderLayout.CENTER); + } + + + public void startUsing() + { + logArea.setText(""); + } + + public void stopUsing() + { + editor = null; + } + + public JPanel getEditorPanel() + { + return panel; + } + + public String getSource() + { + try + { + String source = editor.getSource(); + logInfo("Source code uploaded to server."); + return source; + } + catch (IOException exception) + { + logError("failed to open file source file for reading"); + return ""; + } + } + + public void setSource(String source) + { + try + { + editor.setSource(source); + logInfo("source set"); + } + catch (IOException exception) + { + logError("failed setting the source"); + return; + } + } + + public void setProblemComponent(ProblemComponentModel component, Language language, Renderer renderer) + { + try + { + editor = new Editor(component, language, renderer); + } + catch (IOException exception) + { + logError("failed while loading the problem"); + } + } + + public void setTextEnabled(Boolean enable) + { + editor.setTextEnabled(enable); + } + + + private void log(String what) + { + SimpleDateFormat format = new SimpleDateFormat("kk:mm:ss"); + String time = format.format(new Date()); + logArea.append(time + ", " + what); + } + + private void logInfo(String what) + { + log(" INFO: " + what + "\n"); + } + + private void logWarning(String what) + { + log(" WARN: " + what + "\n"); + } + + private void logError(String what) + { + log("ERROR: " + what + "\n"); + } + + + public static void main(String args[]) + { + VimCoder plugin = new VimCoder(); + + JFrame frame = new JFrame("VimCoder"); + frame.add(plugin.getEditorPanel()); + frame.setSize(640, 480); + frame.setVisible(true); + frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE); + + plugin.startUsing(); + } +} + diff --git a/src/com/dogcows/resources/C++Driver b/src/com/dogcows/resources/C++Driver new file mode 100644 index 0000000..9f25901 --- /dev/null +++ b/src/com/dogcows/resources/C++Driver @@ -0,0 +1,102 @@ + +#include "$CLASSNAME$.cc" + +#include +#include +#include + +static bool __exit_on_fail = false; +static int __pass = 0; +static int __fail = 0; +static double __time = 0.0; + +static void __timer_start() +{ + struct timeval tv; + if (gettimeofday(&tv, NULL) == 0) + { + __time = (double)tv.tv_sec + (double)tv.tv_usec * 0.000001; + } +} + +static double __timer_stop() +{ + double start = __time; + __timer_start(); + return __time - start; +} + +template +std::string __encode(const T& in) +{ + std::ostringstream s; + s << in; + return s.str(); +} + +std::string __encode(const std::string& in) +{ + std::ostringstream s; + s << '"' << in << '"'; + return s.str(); +} + +template +std::string __join(const std::vector& in, const std::string& glue) +{ + if (in.size() == 0) return ""; + std::ostringstream s; + s << __encode(in[0]); + for (size_t i = 1; i < in.size(); ++i) s << glue << __encode(in[i]); + return s.str(); +} + +template +std::string __encode(const std::vector& in) +{ + std::ostringstream s; + s << "{ " << __join(in, ", ") << " }"; + return s.str(); +} + +void __do_test(const std::string& expected, $METHODPARAMS$) +{ + static int testNum = 0; + std::cout << "----------------------------------------" << std::endl + << "Test " << testNum++ << ": "; + + __timer_start(); + + $CLASSNAME$ object; + $RETURNTYPE$ ret = object.$METHODNAME$($METHODPARAMNAMES$); + + double t = __timer_stop(); + + std::string actual = __encode(ret); + if (actual == expected) + { + std::cout << "[PASS] in " << t << " seconds." << std::endl; + ++__pass; + } + else + { + std::cout << "[FAIL] in " << t << " seconds." << std::endl + << " Actual: " << actual << std::endl + << " Expected: " << expected << std::endl; + ++__fail; + if (__exit_on_fail) exit(1); + } +} + +int main(int argc, char* argv[]) +{ + if (1 < argc) __exit_on_fail = true; + +$TESTCASES$ + std::cout << "========================================" << std::endl + << " Total Pass: " << __pass << std::endl + << " Total Fail: " << __fail << std::endl; + + return __fail; +} + diff --git a/src/com/dogcows/resources/C++Makefile b/src/com/dogcows/resources/C++Makefile new file mode 100644 index 0000000..b1cb4ed --- /dev/null +++ b/src/com/dogcows/resources/C++Makefile @@ -0,0 +1,17 @@ + +CXX := g++ +CXXFLAGS := -O0 -g -Wall + +all: driver + +run: all + ./driver + +test: all + ./driver -exit_on_fail + +driver: driver.o + $(CXX) $(CXXFLAGS) $< -o $@ $(LDFLAGS) $(LIBS) + +.PHONY: all run test + diff --git a/src/com/dogcows/resources/C++Template b/src/com/dogcows/resources/C++Template new file mode 100644 index 0000000..e106cb5 --- /dev/null +++ b/src/com/dogcows/resources/C++Template @@ -0,0 +1,42 @@ + +// {{{ Boilerplate Code <-------------------------------------------------- +// +// vim:filetype=cpp foldmethod=marker foldmarker={{{,}}} + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define FOR(I,A,B) for(int I = (A); I < (B); ++I) +#define REP(I,N) FOR(I,0,N) +#define ALL(A) (A).begin(), (A).end() + +using namespace std; + +// }}} + +class $CLASSNAME$ +{ +public: + $RETURNTYPE$ $METHODNAME$($METHODPARAMS$) + { + $CARAT$ + } +}; + diff --git a/src/com/dogcows/resources/C++Test b/src/com/dogcows/resources/C++Test new file mode 100644 index 0000000..4316d38 --- /dev/null +++ b/src/com/dogcows/resources/C++Test @@ -0,0 +1 @@ + __do_test($TESTOUTPUT$, $TESTINPUTS$);