/** * Copyright (C) 2005 - 2012 Eric Van Dewoestine * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see <http://www.gnu.org/licenses/>. */ package org.eclim.installer.step; import java.awt.Component; import java.awt.event.ActionEvent; import java.awt.event.ActionListener; import java.io.FileInputStream; import java.util.ArrayList; import java.util.Properties; import javax.swing.JCheckBox; import javax.swing.JFileChooser; import javax.swing.JLabel; import javax.swing.JList; import javax.swing.JPanel; import javax.swing.JScrollPane; import javax.swing.JTextField; import javax.swing.ListSelectionModel; import javax.swing.event.ListSelectionEvent; import javax.swing.event.ListSelectionListener; import javax.swing.filechooser.FileFilter; import org.apache.commons.io.IOUtils; import org.apache.commons.lang.StringUtils; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.taskdefs.Delete; import org.apache.tools.ant.taskdefs.condition.Os; import org.apache.tools.ant.types.FileSet; import org.formic.InstallContext; import org.formic.Installer; import org.formic.util.CommandExecutor; import org.formic.util.File; import org.formic.util.dialog.gui.GuiDialogs; import org.formic.wizard.form.GuiForm; import org.formic.wizard.form.Validator; import org.formic.wizard.form.gui.component.FileChooser; import org.formic.wizard.form.validator.ValidatorBuilder; import org.formic.wizard.step.AbstractGuiStep; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import foxtrot.Task; import foxtrot.Worker; import net.miginfocom.swing.MigLayout; /** * Step for choosing the vimfiles directory to install vim scripts in. * * @author Eric Van Dewoestine */ public class VimStep extends AbstractGuiStep { private static final Logger logger = LoggerFactory.getLogger(VimStep.class); private static final String[] WINDOWS_VIMS = { "vim.bat", "gvim.bat", "vim.exe", "gvim.exe", "C:/Program Files (x86)/Vim/vim73/vim.exe", "C:/Program Files (x86)/Vim/vim73/gvim.exe", "C:/Program Files (x86)/Vim/vim72/vim.exe", "C:/Program Files (x86)/Vim/vim72/gvim.exe", "C:/Program Files/Vim/vim73/vim.exe", "C:/Program Files/Vim/vim73/gvim.exe", "C:/Program Files/Vim/vim72/vim.exe", "C:/Program Files/Vim/vim72/gvim.exe", "C:/Program Files/Vim/vim71/vim.exe", "C:/Program Files/Vim/vim71/gvim.exe", "C:/Program Files/Vim/vim70/vim.exe", "C:/Program Files/Vim/vim70/gvim.exe", }; private static final String[] WINDOWS_GVIMS = { "C:/Program Files (x86)/Vim/vim73/gvim.exe", "C:/Program Files (x86)/Vim/vim72/gvim.exe", "C:/Program Files/Vim/vim73/gvim.exe", "C:/Program Files/Vim/vim72/gvim.exe", "C:/Program Files/Vim/vim71/gvim.exe", "C:/Program Files/Vim/vim70/gvim.exe", }; private static final String[] UNIX_VIMS = {"vim", "gvim"}; private static final String COMMAND = "redir! > <file> | silent! echo &rtp | quit"; private JPanel panel; private FileChooser fileChooser; private JList dirList; private JCheckBox skipCheckBox; private boolean rtpAttempted; private boolean homeVimCreatePrompted; private String[] runtimePath; /** * Constructs the step. */ public VimStep(String name, Properties properties) { super(name, properties); } /** * {@inheritDoc} * @see org.formic.wizard.step.GuiStep#init() */ public Component init() { GuiForm form = createForm(); String files = fieldName("files"); fileChooser = new FileChooser(JFileChooser.DIRECTORIES_ONLY); // allow just .vim dirs to not be hidden fileChooser.getFileChooser().setFileHidingEnabled(false); fileChooser.getFileChooser().addChoosableFileFilter(new FileFilter(){ public boolean accept(java.io.File f) { String path = f.getAbsolutePath(); return f.isDirectory() && ( path.matches(".*/\\.vim(/.*|$)") || !path.matches(".*/\\..*")); } public String getDescription() { return null; } }); String skip = fieldName("skip"); skipCheckBox = new JCheckBox(Installer.getString(skip)); skipCheckBox.addActionListener(new ActionListener(){ public void actionPerformed(ActionEvent e){ boolean selected = ((JCheckBox)e.getSource()).isSelected(); JTextField fileField = fileChooser.getTextField(); fileField.setEnabled(!selected); fileChooser.getButton().setEnabled(!selected); if (dirList != null){ dirList.setEnabled(!selected); } // hacky Validator validator = (Validator) fileField.getClientProperty("validator"); setValid(selected || validator.isValid(fileField.getText())); } }); panel = new JPanel(new MigLayout( "wrap 2", "[fill]", "[] [] [] [fill, grow]")); panel.add(form.createMessagePanel(), "span"); panel.add(new JLabel(Installer.getString(files)), "split"); panel.add(fileChooser, "skip"); panel.add(skipCheckBox, "span"); form.bind(files, fileChooser.getTextField(), new ValidatorBuilder() .required() .isDirectory() .fileExists() .isWritable() .validator()); return panel; } /** * {@inheritDoc} * @see org.formic.wizard.WizardStep#displayed() */ public void displayed() { if(!rtpAttempted){ rtpAttempted = true; setBusy(true); try{ runtimePath = (String[])Worker.post(new Task(){ public Object run() throws Exception { setGvimProperty(); return getVimRuntimePath(); } }); // filter out dirs the user doesn't have permission write to. ArrayList<String> filtered = new ArrayList<String>(); if (runtimePath != null){ for (String path : runtimePath){ if (new File(path).canWrite()){ if (Installer.isUninstall()){ File eclimDir = new File(path + "/eclim"); if (eclimDir.exists()){ if (eclimDir.canWrite()){ filtered.add(path); }else{ logger.warn( path + "/eclim is not writable by the current user"); } } }else{ filtered.add(path); } } } } String[] rtp = filtered.toArray(new String[filtered.size()]); if(rtp == null || rtp.length == 0){ if(!Installer.isUninstall()){ if(!homeVimCreatePrompted){ createUserVimFiles("No suitable vim files directory found."); }else{ GuiDialogs.showWarning( "Your vim install is still reporting no\n" + "suitable vim files directories.\n" + "You will need to manually specify one."); } } }else{ if(rtp.length == 1){ fileChooser.getTextField().setText(rtp[0]); // try to discourage windows users from installing eclim files in // their vim installation. if(new File(rtp[0] + "/gvim.exe").exists()){ createUserVimFiles("No user vim files directory found."); } }else{ dirList = new JList(rtp); dirList.setSelectionMode(ListSelectionModel.SINGLE_SELECTION); JScrollPane scrollPane = new JScrollPane(dirList); panel.add(scrollPane, "span, grow"); dirList.addListSelectionListener(new ListSelectionListener(){ public void valueChanged(ListSelectionEvent event){ if(!event.getValueIsAdjusting()){ fileChooser.getTextField() .setText((String)dirList.getSelectedValue()); } } }); dirList.setSelectedIndex(0); } } }catch(Exception e){ e.printStackTrace(); } setBusy(false); fileChooser.getTextField().grabFocus(); } } /** * {@inheritDoc} * @see org.formic.wizard.WizardStep#proceed() */ public boolean proceed() { boolean proceed = super.proceed(); if (proceed){ InstallContext context = Installer.getContext(); context.setValue("vim.skip", Boolean.valueOf(skipCheckBox.isSelected())); String vimfiles = (String)context.getValue("vim.files"); if (vimfiles != null){ vimfiles = vimfiles.replace('\\', '/'); context.setValue("vim.files", vimfiles); // Check if the user has the eclim vim files already installed in // another directory in their vim's runtime path. // on windows, since case is insensitive, lower the path. if (Os.isFamily(Os.FAMILY_WINDOWS)){ vimfiles = vimfiles.toLowerCase(); } if(runtimePath != null && runtimePath.length > 0){ for (String rpath : runtimePath){ String path = rpath; if (Os.isFamily(Os.FAMILY_WINDOWS)){ path = path.toLowerCase(); } if (vimfiles.equals(path)){ continue; } File fpath = new File(path + "/plugin/eclim.vim"); if (!fpath.exists()){ continue; } if (fpath.canWrite()){ boolean remove = GuiDialogs.showConfirm( "You appear to have one or more of the eclim vim files\n" + "installed in another directory:\n" + " " + rpath + "\n" + "Would you like the installer to remove those files now?"); if (remove){ Delete delete = new Delete(); delete.setProject(Installer.getProject()); delete.setTaskName("delete"); delete.setIncludeEmptyDirs(true); delete.setFailOnError(true); FileSet set = new FileSet(); set.setDir(new File(path + "/eclim")); set.createInclude().setName("**/*"); set.createExclude().setName("after/**/*"); set.createExclude().setName("resources/**/*"); delete.addFileset(set); try{ boolean deleted = fpath.delete(); if (!deleted){ throw new BuildException( "Failed to delete file: plugin/eclim.vim"); } delete.execute(); }catch(BuildException be){ GuiDialogs.showError( "Failed to delete old eclim vim files:\n" + " " + be.getMessage() + "\n" + "You may continue with the installation, but if old eclim\n" + "vim files remain, chances are that you will receive\n" + "errors upon starting (g)vim and the older version of\n" + "the files may take precedence over the ones you are\n" + "installing now, leading to indeterminate behavior."); } } proceed = remove; }else{ GuiDialogs.showWarning( "You appear to have one or more of the eclim vim files\n" + "installed in another directory:\n" + " " + rpath + "\n" + "Unfortunately it seems you do not have write access to\n" + "that directory. You may continue with the installation,\n" + "but chances are that you will receive errors upon starting\n" + "(g)vim and the older version of the files may take precedence\n" + "over the ones you are installing now, leading to indeterminate\n" + "behavior."); } } } } } return proceed; } /** * Attempt to find where gvim is installed. */ private void setGvimProperty() { try{ String[] gvims = null; if(Os.isFamily(Os.FAMILY_WINDOWS)){ gvims = WINDOWS_GVIMS; for (String gvim : gvims){ if (new File(gvim).isFile()){ Installer.getProject().setProperty("eclim.gvim", gvim); break; } } }else{ String vim = Os.isFamily(Os.FAMILY_MAC) ? "mvim" : "gvim"; CommandExecutor executor = CommandExecutor.execute(new String[]{"which", vim}, 1000); if(executor.getReturnCode() == 0){ String result = executor.getResult().trim(); logger.info("which " + vim + ": " + result); Installer.getProject().setProperty("eclim.gvim", result); }else{ logger.info("which " + vim + ':' + " out=" + executor.getResult() + " err=" + executor.getErrorMessage()); } } }catch(Exception e){ e.printStackTrace(); } } /** * Prompt the user to create the standard user local vim files directory. * * @param message The message indicating the primary reason we're asking them * if they want to create the user local directory. */ private void createUserVimFiles(String message) { homeVimCreatePrompted = true; File vimfiles = new File( System.getProperty("user.home") + '/' + (Os.isFamily(Os.FAMILY_WINDOWS) ? "vimfiles" : ".vim")); System.out.println( "Checking for user vim files directory: " + vimfiles); if(!vimfiles.exists()){ boolean create = GuiDialogs.showConfirm( message + "\n" + "Would you like to create the standard\n" + "directory for your system?\n" + vimfiles); if(create){ boolean created = vimfiles.mkdir(); if(created){ rtpAttempted = false; displayed(); }else{ GuiDialogs.showError("Unable to create directory: " + vimfiles); } } }else{ fileChooser.getTextField().setText( vimfiles.getAbsolutePath().replace('\\', '/')); } } /** * Attempts to determine available paths in vim's runtime path. * * @return Array of paths or null if unable to determine any. */ private String[] getVimRuntimePath() { try{ java.io.File tempFile = File.createTempFile("eclim_installer", null); String command = COMMAND.replaceFirst("<file>", tempFile.getAbsolutePath().replace('\\', '/').replaceAll(" ", "\\ ")); String[] vims = null; if(Os.isFamily(Os.FAMILY_WINDOWS)){ vims = WINDOWS_VIMS; }else{ vims = UNIX_VIMS; } String[] args = { null, "-f", "-X", "-u", "NONE", "-U", "NONE", "--cmd", command, }; for (int ii = 0; ii < vims.length; ii++){ args[0] = vims[ii]; CommandExecutor executor = CommandExecutor.execute(args, 5000); if(executor.getReturnCode() == 0){ return parseVimRuntimePathResults(tempFile); } if (executor.isShutdown()){ return null; } executor.destroy(); } }catch(Exception e){ GuiDialogs.showError("Error determining your vim runtime path.", e); } return null; } /** * Parses the results of echoing vim runtime path to a file. * * @param file The file containing the results. * @return The results. */ private String[] parseVimRuntimePathResults(java.io.File file) { FileInputStream in = null; try{ String contents = IOUtils.toString(in = new FileInputStream(file)); String[] paths = StringUtils.stripAll(StringUtils.split(contents, ',')); ArrayList<String> results = new ArrayList<String>(); for (String path : paths){ if(new File(path).isDirectory()){ results.add(path.replace('\\', '/')); } } return results.toArray(new String[results.size()]); }catch(Exception e){ e.printStackTrace(); }finally{ IOUtils.closeQuietly(in); file.deleteOnExit(); file.delete(); } return null; } }