/*
* Vimplugin
*
* Copyright (c) 2007 - 2011 by The Vimplugin Project.
*
* Released under the GNU General Public License
* with ABSOLUTELY NO WARRANTY.
*
* See the file COPYING for more information.
*/
package org.vimplugin;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashSet;
import org.eclim.logging.Logger;
import org.eclim.util.CommandExecutor;
import org.eclipse.core.runtime.Platform;
import org.vimplugin.editors.VimEditor;
import org.vimplugin.preferences.PreferenceConstants;
/**
* Class that implements as much as of the Vim Server functions as possible so
* that VimServer and VimServerNewWindow can hopefully be combined eventually to
* one class or at least reduced to very tiny class which just extend this class
* in a trivial manner.
*/
public class VimServer
{
private static final Logger logger = Logger.getLogger(VimServer.class);
private boolean embedded;
private boolean tabbed;
/**
* the id of this instance. IDs are counted in
* {@link org.vimplugin.VimPlugin#nextServerID Vimplugin}.
*/
private final int ID;
/**
* The editors associated with the vim instance. For same window opening.
*/
private HashSet<VimEditor> editors = new HashSet<VimEditor>();
/**
* Initialise the class.
*
* @param instanceID The ID for this VimServer.
*/
public VimServer(int instanceID) {
ID = instanceID;
}
/**
* The Vim process.
*/
protected Process p;
/**
* The thread used to communicate with vim (runs {@link #vc}).
*/
protected Thread t;
/**
* Used to communicate with vim.
*/
protected VimConnection vc = null;
public int getID() {
return ID;
}
/**
* @return The {@link VimConnection} Used to communicate with this Vim
* instance
*/
public VimConnection getVc() {
return vc;
}
public boolean isExternalTabbed() {
return tabbed;
}
public boolean isEmbedded() {
return embedded;
}
/**
* Gives the vim argument with the port depending on the portID.
*
* @param portID
* @return The argument for vim for starting the Netbeans interface.
*/
protected String getNetbeansString(int portID) {
int port = VimPlugin.getDefault().getPreferenceStore().getInt(
PreferenceConstants.P_PORT)
+ portID;
return "-nb::" + port;
}
/**
* Get netbeans-port,host and pass and start vim with -nb option.
*
* @param workingDir
* @param filePath
* @param tabbed
* @param first
*/
public void start(
String workingDir, String filePath, boolean tabbed, boolean first)
{
String gvim = VimPlugin.getDefault().getPreferenceStore().getString(
PreferenceConstants.P_GVIM);
String[] addopts = getUserArgs();
String[] args = null;
if (!tabbed || first){
int numArgs = tabbed ? 8 : 6;
args = new String[numArgs + addopts.length];
// NOTE: for macvim, the --servername arg must be before the netbeans arg
// NOTE: for macvim, the --cmd args must be before the netbeans arg (at
// least w/ snapshot 62 on lion)
args[0] = gvim;
args[1] = "--cmd";
args[2] = "let g:vimplugin_running = 1";
int offset = 0;
if (tabbed){
offset = 2;
args[3] = "--cmd";
args[4] = "let g:vimplugin_tabbed = 1";
}
args[3 + offset] = "--servername";
args[4 + offset] = String.valueOf(ID);
args[5 + offset] = getNetbeansString(ID);
System.arraycopy(addopts, 0, args, numArgs, addopts.length);
this.tabbed = tabbed;
start(workingDir, false, (tabbed && !first), args);
}else{
args = new String[5 + addopts.length];
args[0] = gvim;
args[1] = "--servername";
args[2] = String.valueOf(ID);
args[3] = "--remote-send";
args[4] = ":tabnew<cr>:Tcd " + workingDir.replace(" ", "\\ ") + "<cr>";
System.arraycopy(addopts, 0, args, 5, addopts.length);
start(workingDir, false, (tabbed && !first), args);
// wait on file to finish opening
// on windows we need to use vim.exe instead of gvim.exe otherwise popups
// will be generated.
String vim = gvim;
if (Platform.getOS().equals(Platform.OS_WIN32)){
vim = gvim.replace("gvim.exe", "vim.exe");
}
args = new String[5];
args[0] = vim;
args[1] = "--servername";
args[2] = String.valueOf(ID);
args[3] = "--remote-expr";
args[4] = "bufname('%')";
int tries = 0;
while(tries < 5){
try{
String result = CommandExecutor.execute(args, 1000).getResult().trim();
if(filePath.equals(result)){
break;
}
Thread.sleep(500);
tries++;
}catch(Exception e){
logger.error("Error waiting on vim tab to open:", e);
}
}
}
}
/**
* Start vim and embed it in the Window with the <code>wid</code>
* (platform-dependent!) given.
*
* @param workingDir
* @param wid The id of the window to embed vim into
*/
public void start(String workingDir, long wid) {
// gather Strings (nice names for readbility)
String gvim = VimPlugin.getDefault().getPreferenceStore().getString(
PreferenceConstants.P_GVIM);
String netbeans = getNetbeansString(ID);
String dontfork = "-f"; // foreground -- dont fork
// Platform specific code
String socketid = "--socketid";
// use --windowid, under win32
if (Platform.getOS().equals(Platform.OS_WIN32)) {
socketid = "--windowid";
}
String stringwid = String.valueOf(wid);
String[] addopts = getUserArgs();
// build args-array (dynamic size due to addopts.split)
String[] args = new String[9 + addopts.length];
args[0] = gvim;
args[1] = "--servername";
args[2] = String.valueOf(ID);
args[3] = netbeans;
args[4] = dontfork;
args[5] = socketid;
args[6] = stringwid;
args[7] = "--cmd";
args[8] = "let g:vimplugin_running = 1";
// copy addopts to args
System.arraycopy(addopts, 0, args, 9, addopts.length);
start(workingDir, true, false, args);
}
/**
* start gvim with args using a ProcessBuilder and setup
* {@link #vc VimConnection} .
*
* @param args
*/
private void start(
String workingDir, boolean embedded, boolean tabbed, String... args)
{
if (!tabbed && vc != null && vc.isServerRunning()){
return;
}
VimPlugin plugin = VimPlugin.getDefault();
if (vc == null || !vc.isServerRunning()){
vc = new VimConnection(ID);
t = new Thread(vc);
t.setUncaughtExceptionHandler(new VimExceptionHandler());
t.setDaemon(true);
t.start();
}
try {
logger.debug("Trying to start vim");
logger.debug(Arrays.toString(args));
ProcessBuilder builder = new ProcessBuilder(args);
/*java.util.Map<String, String> env = builder.environment();
env.put("SPRO_GVIM_DEBUG", "/tmp/netbeans.log");
env.put("SPRO_GVIM_DLEVEL", "0xffffffff");*/
if (workingDir != null){
builder.directory(new File(workingDir));
}
p = builder.start();
logger.debug("Started vim");
} catch (IOException e) {
logger.error("error:", e);
}
// Waits until server starts.. vim should return startupDone
long maxTime = System.currentTimeMillis() + 10000L; // 10 seconds
while (!vc.isServerRunning()) {
if (System.currentTimeMillis() >= maxTime){
try{
vc.close();
}catch(Exception e){
logger.error("error:", e);
}
String message = plugin.getMessage(
"gvim.startup.failed",
plugin.getMessage("gvim.startupDone.event"));
throw new RuntimeException(message);
}
// sleep so that we don't have a messy cpu-hogging infinite loop
// here
long stoptime = 2000L; // 2 Seconds
logger.debug("Waiting to connect to vim server");
try {
Thread.sleep(stoptime);
} catch (InterruptedException e) {
e.printStackTrace();
}
}
this.embedded = embedded;
}
/**
* Stops the server.. closes the vimconnection
*
* @return Success
*/
public synchronized boolean stop() throws IOException {
boolean result = false; // If error raised
if (p != null){
// give the process some time to finish up before destroying it.
Thread waiter = new Thread(){
public void run(){
try{
logger.debug("Waiting on vim to exit normally...");
p.waitFor();
}catch(InterruptedException ie){
// ignore
}
}
};
waiter.start();
try{
waiter.join(10000); // wait up to 10 seconds.
}catch(InterruptedException ie){
// ignore
}
p.destroy();
p = null;
logger.debug("Vim closed.");
}
if (vc != null){
result = vc.close();
vc = null;
}
if (t != null){
t.interrupt();
}
return result;
}
/**
* Simple setter.
*
* @param editors the editors to set
*/
public void setEditors(HashSet<VimEditor> editors) {
this.editors = editors;
}
/**
* Simple getter.
*
* @return the editors
*/
public HashSet<VimEditor> getEditors() {
return editors;
}
/**
* Gets an {@link VimEditor} by the vim buffer-id.
*
* @param bufid the id to lookup
* @return the corresponding editor or null if none is found.
*/
public VimEditor getEditor(int bufid) {
for (VimEditor veditor : getEditors()) {
if (veditor.getBufferID() == bufid) {
return veditor;
}
}
return null;
}
/**
* Gets the user supplied gvim arguments from the preferences.
*
* @return Array of arguments to be passed to gvim.
*/
protected String[] getUserArgs(){
String opts = VimPlugin.getDefault().getPreferenceStore()
.getString(PreferenceConstants.P_OPTS);
// FIXME: doesn't currently handle escaped spaces/quotes
char[] chars = opts.toCharArray();
char quote = ' ';
StringBuffer arg = new StringBuffer();
ArrayList<String> args = new ArrayList<String>();
for(char c : chars){
if (c == ' ' && quote == ' '){
if (arg.length() > 0){
args.add(arg.toString());
arg = new StringBuffer();
}
}else if (c == '"' || c == '\''){
if (quote != ' ' && c == quote){
quote = ' ';
}else if (quote == ' '){
quote = c;
}else{
arg.append(c);
}
}else{
arg.append(c);
}
}
if (arg.length() > 0){
args.add(arg.toString());
}
return args.toArray(new String[args.size()]);
}
}