In progress: [ Issue 44 ] Add ability to hyperlink to imported files
https://code.google.com/p/protobuf-dt/issues/detail?id=44

diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java
index 78f284e..ea8f08f 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ImportHyperlink.java
@@ -9,9 +9,17 @@
 
 package com.google.eclipse.protobuf.ui.editor;
 
+import org.eclipse.core.filesystem.EFS;
+import org.eclipse.core.filesystem.IFileStore;
+import org.eclipse.core.resources.IFile;
 import org.eclipse.emf.common.util.URI;
 import org.eclipse.jface.text.IRegion;
 import org.eclipse.jface.text.hyperlink.IHyperlink;
+import org.eclipse.ui.*;
+import org.eclipse.ui.ide.IDE;
+import org.eclipse.ui.part.FileEditorInput;
+
+import com.google.eclipse.protobuf.ui.util.Resources;
 
 /**
  * A hyperlink for imported .proto files.
@@ -21,25 +29,13 @@
 class ImportHyperlink implements IHyperlink {
 
   private final URI importUri;
-  private final String uriText;
   private final IRegion region;
+  private final Resources resources;
 
-  ImportHyperlink(URI importUri, String uriText, IRegion region) {
+  ImportHyperlink(URI importUri, IRegion region, Resources resources) {
     this.importUri = importUri;
-    this.uriText = uriText;
     this.region = region;
-  }
-
-  public IRegion getHyperlinkRegion() {
-    return region;
-  }
-
-  public String getTypeLabel() {
-    return "type";
-  }
-
-  public String getHyperlinkText() {
-    return "text";
+    this.resources = resources;
   }
 
   public void open() {
@@ -49,10 +45,35 @@
   }
 
   private void openFromWorkspace() {
-    System.out.println("open from Workspace");
+    IFile file = resources.file(importUri);
+    IEditorInput editorInput = new FileEditorInput(file);
+    IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+    try {
+      page.openEditor(editorInput, "com.google.eclipse.protobuf.Protobuf");
+    } catch (PartInitException e) {
+      e.printStackTrace();
+    }
   }
 
   private void openFromFileSystem() {
-    System.out.println("open from file system");
+    IFileStore fileStore = EFS.getLocalFileSystem().getStore(resources.pathOf(importUri));
+    IWorkbenchPage page = PlatformUI.getWorkbench().getActiveWorkbenchWindow().getActivePage();
+    try {
+      IDE.openEditorOnFileStore(page, fileStore);
+    } catch (PartInitException e) {
+      e.printStackTrace();
+    }
+  }
+
+  public String getTypeLabel() {
+    return null;
+  }
+
+  public IRegion getHyperlinkRegion() {
+    return region;
+  }
+
+  public String getHyperlinkText() {
+    return null;
   }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java
index 2d29674..729c483 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/editor/ProtobufHyperlinkDetector.java
@@ -22,6 +22,7 @@
 import org.eclipse.xtext.util.concurrent.IUnitOfWork;
 
 import com.google.eclipse.protobuf.protobuf.Import;
+import com.google.eclipse.protobuf.ui.util.Resources;
 import com.google.inject.Inject;
 
 /**
@@ -32,13 +33,18 @@
  */
 public class ProtobufHyperlinkDetector extends DefaultHyperlinkDetector {
 
+  private static final IHyperlink[] NO_HYPERLINKS = null;
+
+  private static final char QUOTE = '\"';
+
   @Inject private EObjectAtOffsetHelper eObjectAtOffsetHelper;
+  @Inject private Resources resources;
 
   @Override public IHyperlink[] detectHyperlinks(ITextViewer textViewer, final IRegion region,
       final boolean canShowMultipleHyperlinks) {
     IXtextDocument document = (IXtextDocument)textViewer.getDocument();
     IHyperlink[] importHyperlinks = importHyperlinks(document, region);
-    if (importHyperlinks != null) return importHyperlinks;
+    if (importHyperlinks != NO_HYPERLINKS) return importHyperlinks;
     return document.readOnly(new IUnitOfWork<IHyperlink[], XtextResource>() {
       public IHyperlink[] exec(XtextResource resource) {
         return getHelper().createHyperlinksByOffset(resource, region.getOffset(), canShowMultipleHyperlinks);
@@ -50,18 +56,28 @@
     return document.readOnly(new IUnitOfWork<IHyperlink[], XtextResource>() {
       public IHyperlink[] exec(XtextResource resource) {
         EObject resolved = eObjectAtOffsetHelper.resolveElementAt(resource, region.getOffset());
-        if (!(resolved instanceof Import)) return null;
-        Import anImport = (Import) resolved;
+        if (!(resolved instanceof Import)) return NO_HYPERLINKS;
+        IRegion importUriRegion;
         try {
-          int lineNumber = document.getLineOfOffset(region.getOffset());
-          int lineLength = document.getLineLength(lineNumber);
-          document.get(region.getOffset(), lineLength - region.getOffset());
+          importUriRegion = importUriRegion(document, region.getOffset());
         } catch (BadLocationException e) {
+          return NO_HYPERLINKS;
         }
-        String importUri = anImport.getImportURI();
-        IHyperlink hyperlink = new ImportHyperlink(createURI(importUri), importUri, region);
+        String importUri = ((Import) resolved).getImportURI();
+        IHyperlink hyperlink = new ImportHyperlink(createURI(importUri), importUriRegion, resources);
         return new IHyperlink[] { hyperlink };
       }
     });
   }
+
+  private IRegion importUriRegion(final IXtextDocument document, final int offset) throws BadLocationException {
+    int lineNumber = document.getLineOfOffset(offset);
+    int lineLength = document.getLineLength(lineNumber);
+    int lineOffset = document.getLineOffset(lineNumber);
+    String line = document.get(lineOffset, lineLength);
+    int openingQuoteIndex = line.indexOf(QUOTE);
+    int closingQuoteIndex = line.indexOf(QUOTE, ++openingQuoteIndex);
+    String importUri = line.substring(openingQuoteIndex, closingQuoteIndex);
+    return new Region(lineOffset + openingQuoteIndex, importUri.length());
+  }
 }
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileResolverStrategies.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileResolverStrategies.java
index 831688c..7a84aef 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileResolverStrategies.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileResolverStrategies.java
@@ -14,6 +14,7 @@
 import java.util.Map;
 
 import com.google.eclipse.protobuf.ui.preferences.paths.PathResolutionType;
+import com.google.eclipse.protobuf.ui.util.Resources;
 import com.google.inject.Inject;
 import com.google.inject.Singleton;
 
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
index 22dc6c6..83ac24a 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/FileUriResolver.java
@@ -16,6 +16,7 @@
 
 import com.google.eclipse.protobuf.scoping.IFileUriResolver;
 import com.google.eclipse.protobuf.ui.preferences.paths.*;
+import com.google.eclipse.protobuf.ui.util.Resources;
 import com.google.inject.Inject;
 
 /**
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/MultipleDirectoriesFileResolver.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/MultipleDirectoriesFileResolver.java
index 6379c7d..f07108f 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/MultipleDirectoriesFileResolver.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/MultipleDirectoriesFileResolver.java
@@ -15,6 +15,7 @@
 
 import com.google.eclipse.protobuf.ui.preferences.paths.DirectoryPath;
 import com.google.eclipse.protobuf.ui.preferences.paths.PathsPreferences;
+import com.google.eclipse.protobuf.ui.util.Resources;
 
 /**
  * @author alruiz@google.com (Alex Ruiz)
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver.java
index b220689..04ccf86 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/SingleDirectoryFileResolver.java
@@ -17,6 +17,7 @@
 import org.eclipse.xtext.util.Pair;
 
 import com.google.eclipse.protobuf.ui.preferences.paths.PathsPreferences;
+import com.google.eclipse.protobuf.ui.util.Resources;
 
 /**
  * @author alruiz@google.com (Alex Ruiz)
diff --git a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/Resources.java b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
similarity index 69%
rename from com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/Resources.java
rename to com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
index dbda15e..abe9f2d 100644
--- a/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/scoping/Resources.java
+++ b/com.google.eclipse.protobuf.ui/src/com/google/eclipse/protobuf/ui/util/Resources.java
@@ -6,10 +6,11 @@
  *
  * http://www.eclipse.org/legal/epl-v10.html
  */
-package com.google.eclipse.protobuf.ui.scoping;
+package com.google.eclipse.protobuf.ui.util;
 
 import org.eclipse.core.resources.*;
-import org.eclipse.core.runtime.*;
+import org.eclipse.core.runtime.IPath;
+import org.eclipse.core.runtime.Path;
 import org.eclipse.emf.common.util.URI;
 
 import com.google.inject.Singleton;
@@ -40,9 +41,23 @@
     return file(fileUri).exists();
   }
 
-  private IFile file(URI uri) {
-    IPath resourcePath = new Path(uri.toPlatformString(true));
+  /**
+   * Returns a handle to file identified by the given URI.
+   * @param uri the given URI.
+   * @return a handle to file identified by the given URI.
+   */
+  public IFile file(URI uri) {
+    IPath resourcePath = pathOf(uri);
     IWorkspaceRoot root = ResourcesPlugin.getWorkspace().getRoot();
     return root.getFile(resourcePath);
   }
+
+  /**
+   * Constructs a new path from the given URI.
+   * @param uri the given URI.
+   * @return the constructed path.
+   */
+  public IPath pathOf(URI uri) {
+    return new Path(uri.toPlatformString(true));
+  }
 }