In progress: [ Issue 40 ] Add support for import resolution across multiple
folders
https://code.google.com/p/protobuf-dt/issues/detail?id=40

Replaced text field with list for names of directories to be included in URI resolution. Code cleanup.
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver_resolveUri_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver_resolveUri_Test.java
deleted file mode 100644
index 9a41d2e..0000000
--- a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver_resolveUri_Test.java
+++ /dev/null
@@ -1,93 +0,0 @@
-/*
- * Copyright (c) 2011 Google Inc.
- *
- * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
- * Public License v1.0 which accompanies this distribution, and is available at
- *
- * http://www.eclipse.org/legal/epl-v10.html
- */
-package com.google.eclipse.protobuf.ui.scoping;
-
-import static com.google.eclipse.protobuf.ui.preferences.paths.PathResolutionType.SINGLE_DIRECTORY;
-import static org.hamcrest.core.IsEqual.equalTo;
-import static org.junit.Assert.assertThat;
-import static org.mockito.Matchers.any;
-import static org.mockito.Mockito.*;
-
-import org.eclipse.core.resources.IProject;
-import org.eclipse.emf.common.util.URI;
-import org.eclipse.emf.ecore.resource.Resource;
-import org.junit.*;
-
-import com.google.eclipse.protobuf.junit.core.XtextRule;
-import com.google.eclipse.protobuf.junit.stubs.ResourceStub;
-import com.google.eclipse.protobuf.scoping.IFileUriResolver;
-import com.google.eclipse.protobuf.ui.preferences.paths.*;
-import com.google.inject.*;
-
-/**
- * Tests for <code>{@link FileUriResolver#resolveUri(String, Resource)}</code>.
- *
- * @author alruiz@google.com (Alex Ruiz)
- */
-public class FileUriResolver_resolveUri_Test {
-
-  @Rule public XtextRule xtext = new XtextRule();
-  
-  private Resource resource;
-  private PathsPreferenceReader preferenceReader;
-  private PathsPreferences preferences;
-  private IProject project;
-  private Resources resources;
-
-  private FileUriResolver resolver;
-
-  @Before public void setUp() {
-    resource = new ResourceStub("platform:/resource/src/proto/person.proto");
-    preferenceReader = mock(PathsPreferenceReader.class);
-    preferences = mock(PathsPreferences.class);
-    project = mock(IProject.class);
-    resources = mock(Resources.class);
-    Module module = new Module() {
-      public void configure(Binder binder) {
-        binder.bind(PathsPreferenceReader.class).toInstance(preferenceReader);
-        binder.bind(Resources.class).toInstance(resources);
-        binder.bind(IFileUriResolver.class).to(FileUriResolver.class);
-      }
-    };
-    Injector injector = xtext.injector().createChildInjector(module);
-    resolver = (FileUriResolver) injector.getInstance(IFileUriResolver.class);
-  }
-
-  @Test public void should_resolve_import_URI_if_missing_scheme() {
-    callStubs(SINGLE_DIRECTORY, true);
-    String resolved = resolver.resolveUri("folder1/address.proto", resource);
-    assertThat(resolved, equalTo("platform:/resource/src/proto/folder1/address.proto"));  
-  }
-  
-  @Test public void should_not_resolve_import_URI_if_not_missing_scheme() {
-    callStubs(SINGLE_DIRECTORY, true);
-    String original = "platform:/resource/src/proto/folder1/address.proto";
-    String resolved = resolver.resolveUri(original, resource);
-    assertThat(resolved, equalTo(original));
-  }
-  
-  @Test public void should_resolve_import_URI_even_if_overlapping_folders_with_resource_URI() {
-    callStubs(SINGLE_DIRECTORY, true);
-    String resolved = resolver.resolveUri("src/proto/folder1/address.proto", resource);
-    assertThat(resolved, equalTo("platform:/resource/src/proto/folder1/address.proto"));  
-  }
-
-  @Test public void should_resolve_import_URI_even_if_overlapping_one_folder_only_with_resource_URI() {
-    callStubs(SINGLE_DIRECTORY, true);
-    String resolved = resolver.resolveUri("src/proto/read-only/address.proto", resource);
-    assertThat(resolved, equalTo("platform:/resource/src/proto/read-only/address.proto"));  
-  }
-
-  private void callStubs(PathResolutionType type, boolean resolvedUriExists) {
-    when(resources.project(resource.getURI())).thenReturn(project);
-    when(preferenceReader.readFromPrefereceStore(project)).thenReturn(preferences);
-    when(preferences.pathResolutionType()).thenReturn(type);
-    when(resources.fileExists(any(URI.class))).thenReturn(resolvedUriExists);
-  }
-}
diff --git a/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver_resolveUri_Test.java b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver_resolveUri_Test.java
new file mode 100644
index 0000000..c1fc418
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui.test/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver_resolveUri_Test.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.scoping;
+
+import static org.eclipse.emf.common.util.URI.createURI;
+import static org.hamcrest.core.IsEqual.equalTo;
+import static org.junit.Assert.assertThat;
+import static org.mockito.Matchers.any;
+import static org.mockito.Mockito.*;
+
+import org.eclipse.core.resources.IProject;
+import org.eclipse.emf.common.util.URI;
+import org.junit.*;
+
+import com.google.eclipse.protobuf.ui.preferences.paths.PathsPreferences;
+
+/**
+ * Tests for <code>{@link SingleDirectoryFileResolver#resolveUri(String, URI, PathsPreferences, IProject)}</code>.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class SingleDirectoryFileResolver_resolveUri_Test {
+
+  private static URI resourceUri;
+  private static PathsPreferences preferences;
+  private static IProject project;
+
+  @BeforeClass public static void setUpOnce() {
+    resourceUri = createURI("platform:/resource/src/proto/person.proto");
+    preferences = mock(PathsPreferences.class);
+    project = mock(IProject.class);
+  }
+
+  private Resources resources;
+  private SingleDirectoryFileResolver resolver;
+
+  @Before public void setUp() {
+    resources = mock(Resources.class);
+    resolver = new SingleDirectoryFileResolver(resources);
+  }
+
+  @Test public void should_resolve_import_URI_if_missing_scheme() {
+    when(resources.fileExists(any(URI.class))).thenReturn(true);
+    String resolved = resolver.resolveUri("folder1/address.proto", resourceUri, preferences, project);
+    assertThat(resolved, equalTo("platform:/resource/src/proto/folder1/address.proto"));
+  }
+
+  @Test public void should_resolve_import_URI_even_if_overlapping_folders_with_resource_URI() {
+    when(resources.fileExists(any(URI.class))).thenReturn(true);
+    String resolved = resolver.resolveUri("src/proto/folder1/address.proto", resourceUri, preferences, project);
+    assertThat(resolved, equalTo("platform:/resource/src/proto/folder1/address.proto"));
+  }
+
+  @Test public void should_resolve_import_URI_even_if_overlapping_one_folder_only_with_resource_URI() {
+    when(resources.fileExists(any(URI.class))).thenReturn(true);
+    String resolved = resolver.resolveUri("src/proto/read-only/address.proto", resourceUri, preferences, project);
+    assertThat(resolved, equalTo("platform:/resource/src/proto/read-only/address.proto"));
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
index f8fd406..8f12479 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/compiler/CompilerPreferencePage.java
@@ -8,24 +8,21 @@
  */
 package com.google.eclipse.protobuf.ui.preferences.compiler;
 
-import static com.google.eclipse.protobuf.ui.preferences.compiler.Messages.*;
 import static com.google.eclipse.protobuf.ui.preferences.compiler.CompilerPreferenceNames.*;
-import static org.eclipse.core.runtime.IStatus.OK;
+import static com.google.eclipse.protobuf.ui.preferences.compiler.Messages.*;
 import static org.eclipse.xtext.util.Strings.isEmpty;
 
 import java.io.File;
 
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.*;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.*;
 import org.eclipse.swt.widgets.*;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 
 import com.google.eclipse.protobuf.ui.preferences.PreferenceAndPropertyPage;
-import com.google.eclipse.protobuf.ui.util.FolderNameValidator;
+import com.google.eclipse.protobuf.ui.util.DirectoryNameValidator;
 import com.google.inject.Inject;
 
 /**
@@ -59,8 +56,8 @@
   private Button btnRefreshOutputFolder;
   private Label lblOutputFolderRelative;
 
-  @Inject private FolderNameValidator folderNameValidator;
-  
+  @Inject private DirectoryNameValidator directoryNameValidator;
+
   @Inject public CompilerPreferencePage(IPreferenceStoreAccess preferenceStoreAccess) {
     super(preferenceStoreAccess);
   }
@@ -231,9 +228,9 @@
       pageIsNowInvalid(errorNoOutputFolderName);
       return;
     }
-    IStatus validFolderName = folderNameValidator.validateFolderName(folderName);
-    if (validFolderName.getCode() != OK) {
-      pageIsNowInvalid(validFolderName.getMessage());
+    String invalidDirectoryName = directoryNameValidator.validateDirectoryName(folderName);
+    if (invalidDirectoryName != null) {
+      pageIsNowInvalid(invalidDirectoryName);
       return;
     }
     if (!customPathOptionSelectedAndEnabled()) {
@@ -278,7 +275,7 @@
     enableCompilerOptions(enableCompilerOptions);
     super.performDefaults();
   }
-  
+
   /** {@inheritDoc} */
   @Override protected void onProjectSettingsActivation(boolean active) {
     enableProjectSpecificOptions(active);
@@ -358,7 +355,7 @@
     store.setValue(REFRESH_PROJECT, btnRefreshProject.getSelection());
     store.setValue(REFRESH_OUTPUT_FOLDER, btnRefreshOutputFolder.getSelection());
   }
-  
+
   /** {@inheritDoc} */
   @Override protected String preferencePageId() {
     return PREFERENCE_PAGE_ID;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
new file mode 100644
index 0000000..f056d89
--- /dev/null
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/DirectoryNamesEditor.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright (c) 2011 Google Inc.
+ *
+ * All rights reserved. This program and the accompanying materials are made available under the terms of the Eclipse
+ * Public License v1.0 which accompanies this distribution, and is available at
+ *
+ * http://www.eclipse.org/legal/epl-v10.html
+ */
+package com.google.eclipse.protobuf.ui.preferences.paths;
+
+import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
+import static java.util.Arrays.asList;
+import static java.util.Collections.unmodifiableList;
+import static org.eclipse.jface.window.Window.OK;
+import static org.eclipse.xtext.util.Strings.isEmpty;
+
+import java.util.Collection;
+
+import org.eclipse.jface.dialogs.*;
+import org.eclipse.swt.SWT;
+import org.eclipse.swt.events.*;
+import org.eclipse.swt.layout.*;
+import org.eclipse.swt.widgets.*;
+
+import com.google.eclipse.protobuf.ui.util.DirectoryNameValidator;
+
+/**
+ * Editor where users can add/remove the directories to be used for URI resolution.
+ *
+ * @author alruiz@google.com (Alex Ruiz)
+ */
+public class DirectoryNamesEditor extends Composite {
+
+  private final DirectoryNameValidator directoryNameValidator;
+
+  private List lstDirectoryNames;
+  private final Button btnAdd;
+  private final Button btnRemove;
+  private final Button btnUp;
+  private final Button btnDown;
+
+  private SelectionListener onRemoveListener;
+
+  /**
+   * Creates a new </code>{@link DirectoryNamesEditor}</code>.
+   * @param parent a widget which will be the parent of the new instance (cannot be {@code null}.)
+   * @param directoryNameValidator validates that a {@code String} is a valid directory name.
+   */
+  public DirectoryNamesEditor(Composite parent, DirectoryNameValidator directoryNameValidator) {
+    super(parent, SWT.NONE);
+    this.directoryNameValidator = directoryNameValidator;
+    setLayout(new GridLayout(3, false));
+
+    lstDirectoryNames = new List(this, SWT.BORDER | SWT.H_SCROLL | SWT.V_SCROLL | SWT.MULTI);
+    lstDirectoryNames.setLayoutData(new GridData(SWT.FILL, SWT.FILL, true, true, 1, 1));
+
+    Composite composite = new Composite(this, SWT.NONE);
+    composite.setLayoutData(new GridData(SWT.LEFT, SWT.TOP, false, false, 1, 1));
+    composite.setLayout(new GridLayout(1, false));
+
+    btnAdd = new Button(composite, SWT.NONE);
+    btnAdd.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnAdd.setText(add);
+
+    btnRemove = new Button(composite, SWT.NONE);
+    btnRemove.setEnabled(false);
+    btnRemove.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnRemove.setText(remove);
+
+    btnUp = new Button(composite, SWT.NONE);
+    btnUp.setEnabled(false);
+    btnUp.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnUp.setText(up);
+
+    btnDown = new Button(composite, SWT.NONE);
+    btnDown.setEnabled(false);
+    btnDown.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
+    btnDown.setText(down);
+
+    addEventListeners();
+  }
+
+  private void addEventListeners() {
+    lstDirectoryNames.addDisposeListener(new DisposeListener() {
+      public void widgetDisposed(DisposeEvent e) {
+        lstDirectoryNames = null;
+      }
+    });
+    lstDirectoryNames.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        enableButtonsDependingOnListSelection();
+      }
+    });
+    btnAdd.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        IInputValidator validator = new IInputValidator() {
+          public String isValid(String newText) {
+            if (isEmpty(newText)) return Messages.errorEmptyDirectoryName;
+            return directoryNameValidator.validateDirectoryName(newText);
+          }
+        };
+        InputDialog input = new InputDialog(getShell(), directoryNameInputTitle, directoryNameInputMessage, null, validator);
+        if (input.open() == OK) {
+          lstDirectoryNames.add(input.getValue());
+        }
+      }
+    });
+    btnRemove.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        int index = lstDirectoryNames.getSelectionIndex();
+        if (index < 0) return;
+        lstDirectoryNames.remove(index);
+        enableButtonsDependingOnListSelection();
+      }
+    });
+    btnUp.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        swap(true);
+      }
+    });
+    btnDown.addSelectionListener(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
+        swap(false);
+      }
+    });
+  }
+
+  private void swap(boolean goUp) {
+    int index = lstDirectoryNames.getSelectionIndex();
+    if (index < 0) return;
+    int target = goUp ? index - 1 : index + 1;
+    String[] selection = lstDirectoryNames.getSelection();
+    lstDirectoryNames.remove(index);
+    lstDirectoryNames.add(selection[0], target);
+    lstDirectoryNames.setSelection(target);
+    enableButtonsDependingOnListSelection();
+  }
+
+  /** {@inheritDoc} */
+  @Override public void setEnabled(boolean enabled) {
+    super.setEnabled(enabled);
+    lstDirectoryNames.setEnabled(enabled);
+    btnAdd.setEnabled(enabled);
+    if (enabled) {
+      enableButtonsDependingOnListSelection();
+    } else {
+      btnRemove.setEnabled(false);
+      btnUp.setEnabled(false);
+      btnDown.setEnabled(false);
+    }
+  }
+
+  private void enableButtonsDependingOnListSelection() {
+    int selectionIndex = lstDirectoryNames.getSelectionIndex();
+    int size = lstDirectoryNames.getItemCount();
+    boolean hasSelection = selectionIndex >= 0;
+    btnRemove.setEnabled(hasSelection);
+    boolean hasElements = size > 1;
+    btnUp.setEnabled(hasElements && selectionIndex > 0);
+    btnDown.setEnabled(hasElements && hasSelection && selectionIndex < size - 1);
+  }
+
+  public java.util.List<String> directoryNames() {
+    return unmodifiableList(asList(lstDirectoryNames.getItems()));
+  }
+
+  public void addDirectoryNames(Collection<String> directoryNames) {
+    for (String name : directoryNames) lstDirectoryNames.add(name);
+  }
+
+  public void onRemove(SelectionListener listener) {
+    if (onRemoveListener != null) lstDirectoryNames.removeSelectionListener(onRemoveListener);
+    onRemoveListener = listener;
+    lstDirectoryNames.addSelectionListener(onRemoveListener);
+  }
+}
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
index 83e2534..ef56359 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.java
@@ -14,12 +14,18 @@
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class Messages extends NLS {
-  
-  public static String directoryNameHint;
+
+  public static String add;
+  public static String directoryNameInputMessage;
+  public static String directoryNameInputTitle;
+  public static String down;
+  public static String errorEmptyDirectoryName;
   public static String errorNoDirectoryNames;
   public static String filesInMultipleDirectories;
   public static String filesInOneDirectoryOnly;
   public static String importedFilesPathResolution;
+  public static String remove;
+  public static String up;
 
   static {
     Class<Messages> targetType = Messages.class;
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
index 8424e7e..0322aa3 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/Messages.properties
@@ -1,5 +1,11 @@
-directoryNameHint=*comma-separated values (e.g. 'src, src-gen, src-readonly')
-errorNoDirectoryNames=Enter the name of the directories
-filesInMultipleDirectories=Look for imported files in directories:*
+add=&Add
+directoryNameInputMessage=Enter directory name:
+directoryNameInputTitle=Path Resolution
+down=&Down
+errorEmptyDirectoryName=The name of the directory should not be empty
+errorNoDirectoryNames=Enter the names of the directories
+filesInMultipleDirectories=Look for imported files in directories:
 filesInOneDirectoryOnly=One directory for all .proto files
 importedFilesPathResolution=Path resolution of imported files
+remove=&Remove
+up=&Up
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
index de99706..c2789e0 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/preferences/paths/PathsPreferencePage.java
@@ -10,38 +10,35 @@
 
 import static com.google.eclipse.protobuf.ui.preferences.paths.Messages.*;
 import static com.google.eclipse.protobuf.ui.preferences.paths.PathsPreferenceNames.*;
-import static com.google.eclipse.protobuf.ui.util.Strings.CSV_PATTERN;
-import static org.eclipse.core.runtime.IStatus.OK;
-import static org.eclipse.xtext.util.Strings.isEmpty;
+import static org.eclipse.xtext.util.Strings.*;
 
-import org.eclipse.core.runtime.IStatus;
 import org.eclipse.jface.preference.IPreferenceStore;
 import org.eclipse.swt.SWT;
 import org.eclipse.swt.events.*;
-import org.eclipse.swt.layout.GridData;
-import org.eclipse.swt.layout.GridLayout;
+import org.eclipse.swt.layout.*;
 import org.eclipse.swt.widgets.*;
 import org.eclipse.xtext.ui.editor.preferences.IPreferenceStoreAccess;
 
 import com.google.eclipse.protobuf.ui.preferences.PreferenceAndPropertyPage;
-import com.google.eclipse.protobuf.ui.util.FolderNameValidator;
+import com.google.eclipse.protobuf.ui.util.DirectoryNameValidator;
 import com.google.inject.Inject;
 
 /**
  * Preference page for import paths.
- * 
+ *
  * @author alruiz@google.com (Alex Ruiz)
  */
 public class PathsPreferencePage extends PreferenceAndPropertyPage {
 
+  private static final String COMMA_DELIMITER = ",";
   private static final String PREFERENCE_PAGE_ID = PathsPreferencePage.class.getName();
 
   private Group grpResolutionOfImported;
   private Button btnOneFolderOnly;
   private Button btnMultipleFolders;
-  private Text txtFolderNames;
+  private DirectoryNamesEditor directoryNamesEditor;
 
-  @Inject private FolderNameValidator folderNameValidator;
+  @Inject private DirectoryNameValidator directoryNameValidator;
 
   @Inject public PathsPreferencePage(IPreferenceStoreAccess preferenceStoreAccess) {
     super(preferenceStoreAccess);
@@ -51,30 +48,27 @@
   @Override protected Control createContents(Composite parent) {
     // generated by WindowBuilder
     Composite contents = contentsComposite(parent);
-    
+
     grpResolutionOfImported = new Group(contents, SWT.NONE);
     grpResolutionOfImported.setLayout(new GridLayout(1, false));
     grpResolutionOfImported.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 2, 1));
     grpResolutionOfImported.setText(importedFilesPathResolution);
-    
+
     btnOneFolderOnly = new Button(grpResolutionOfImported, SWT.RADIO);
     btnOneFolderOnly.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
     btnOneFolderOnly.setText(filesInOneDirectoryOnly);
-    
+
     btnMultipleFolders = new Button(grpResolutionOfImported, SWT.RADIO);
     btnMultipleFolders.setLayoutData(new GridData(SWT.LEFT, SWT.CENTER, true, false, 1, 1));
     btnMultipleFolders.setText(filesInMultipleDirectories);
-    
-    txtFolderNames = new Text(grpResolutionOfImported, SWT.BORDER);
-    txtFolderNames.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, true, false, 1, 1));
-    
-    Label label = new Label(grpResolutionOfImported, SWT.NONE);
-    label.setText(directoryNameHint);
+
+    directoryNamesEditor = new DirectoryNamesEditor(grpResolutionOfImported, directoryNameValidator);
+    directoryNamesEditor.setLayoutData(new GridData(SWT.FILL, SWT.CENTER, false, false, 1, 1));
     new Label(contents, SWT.NONE);
-    
+
     updateFromPreferenceStore();
     addEventListeners();
-    
+
     return contents;
   }
 
@@ -82,7 +76,7 @@
     IPreferenceStore store = doGetPreferenceStore();
     btnOneFolderOnly.setSelection(store.getBoolean(FILES_IN_ONE_DIRECTORY_ONLY));
     btnMultipleFolders.setSelection(store.getBoolean(FILES_IN_MULTIPLE_DIRECTORIES));
-    txtFolderNames.setText(store.getString(DIRECTORY_NAMES));
+    setDirectoryNames(store.getString(DIRECTORY_NAMES));
     boolean shouldEnablePathsOptions = true;
     if (isPropertyPage()) {
       boolean useProjectSettings = store.getBoolean(ENABLE_PROJECT_SETTINGS);
@@ -96,35 +90,25 @@
     addSelectionListener(new SelectionAdapter() {
       @Override public void widgetSelected(SelectionEvent e) {
         boolean selected = btnMultipleFolders.getSelection();
-        txtFolderNames.setEnabled(selected);
+        directoryNamesEditor.setEnabled(selected);
         checkState();
       }
     }, btnOneFolderOnly, btnMultipleFolders);
-    txtFolderNames.addModifyListener(new ModifyListener() {
-      public void modifyText(ModifyEvent e) {
+    directoryNamesEditor.onRemove(new SelectionAdapter() {
+      @Override public void widgetSelected(SelectionEvent e) {
         checkState();
       }
     });
   }
 
   private void checkState() {
-    if (txtFolderNames.isEnabled()) {
-      String folderNames = txtFolderNames.getText().trim();
-      if (isEmpty(folderNames)) {
-        pageIsNowInvalid(errorNoDirectoryNames);
-        return;
-      }
-      for (String folderName : folderNames.split(CSV_PATTERN)) {
-        IStatus validFolderName = folderNameValidator.validateFolderName(folderName);
-        if (validFolderName.getCode() != OK) {
-          pageIsNowInvalid(validFolderName.getMessage());
-          return;
-        }
-      }
+    if (directoryNamesEditor.isEnabled() && directoryNamesEditor.directoryNames().isEmpty()) {
+      pageIsNowInvalid(errorNoDirectoryNames);
+      return;
     }
     pageIsNowValid();
   }
-  
+
   /** {@inheritDoc} */
   @Override protected void onProjectSettingsActivation(boolean active) {
     enableProjectOptions(active);
@@ -134,7 +118,7 @@
     IPreferenceStore store = doGetPreferenceStore();
     btnOneFolderOnly.setSelection(store.getDefaultBoolean(FILES_IN_ONE_DIRECTORY_ONLY));
     btnMultipleFolders.setSelection(store.getDefaultBoolean(FILES_IN_MULTIPLE_DIRECTORIES));
-    txtFolderNames.setText(store.getDefaultString(DIRECTORY_NAMES));
+    setDirectoryNames(store.getDefaultString(DIRECTORY_NAMES));
     boolean shouldEnablePathsOptions = true;
     if (isPropertyPage()) {
       boolean useProjectSettings = store.getDefaultBoolean(ENABLE_PROJECT_SETTINGS);
@@ -144,21 +128,29 @@
     enableProjectOptions(shouldEnablePathsOptions);
     super.performDefaults();
   }
-  
+
+  private void setDirectoryNames(String directoryNames) {
+    directoryNamesEditor.addDirectoryNames(split(directoryNames, COMMA_DELIMITER));
+  }
+
   private void enableProjectOptions(boolean enabled) {
     grpResolutionOfImported.setEnabled(enabled);
     btnOneFolderOnly.setEnabled(enabled);
     btnMultipleFolders.setEnabled(enabled);
-    txtFolderNames.setEnabled(btnMultipleFolders.getSelection() && enabled);
+    directoryNamesEditor.setEnabled(btnMultipleFolders.getSelection() && enabled);
   }
-  
+
   /** {@inheritDoc} */
   @Override protected void savePreferences() {
     IPreferenceStore store = getPreferenceStore();
     if (isPropertyPage()) store.setValue(ENABLE_PROJECT_SETTINGS, areProjectSettingsActive());
     store.setValue(FILES_IN_ONE_DIRECTORY_ONLY, btnOneFolderOnly.getSelection());
     store.setValue(FILES_IN_MULTIPLE_DIRECTORIES, btnMultipleFolders.getSelection());
-    store.setValue(DIRECTORY_NAMES, txtFolderNames.getText().trim());
+    store.setValue(DIRECTORY_NAMES, directoryNames());
+  }
+
+  private String directoryNames() {
+    return concat(COMMA_DELIMITER, directoryNamesEditor.directoryNames());
   }
 
   /** {@inheritDoc} */
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/FolderNameValidator.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/DirectoryNameValidator.java
similarity index 66%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/FolderNameValidator.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/DirectoryNameValidator.java
index 752099b..bf5253d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/FolderNameValidator.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/DirectoryNameValidator.java
@@ -9,23 +9,24 @@
 package com.google.eclipse.protobuf.ui.util;
 
 import static org.eclipse.core.resources.IResource.FOLDER;
+import static org.eclipse.core.runtime.IStatus.OK;
 
-import org.eclipse.core.resources.IWorkspace;
-import org.eclipse.core.resources.ResourcesPlugin;
+import org.eclipse.core.resources.*;
 import org.eclipse.core.runtime.IStatus;
 
 import com.google.inject.Singleton;
 
 /**
  * Validates names of folders.
- * 
+ *
  * @author alruiz@google.com (Alex Ruiz)
  */
 @Singleton
-public class FolderNameValidator {
+public class DirectoryNameValidator {
 
-  public IStatus validateFolderName(String folderName) {
+  public String validateDirectoryName(String directoryName) {
     IWorkspace workspace = ResourcesPlugin.getWorkspace();
-    return workspace.validateName(folderName, FOLDER);
+    IStatus isValid = workspace.validateName(directoryName, FOLDER);
+    return (isValid.getCode() == OK) ? null : isValid.getMessage();
   }
 }