diff --git a/pom.xml b/pom.xml
index 39a8f04b..02ca1cd9 100644
--- a/pom.xml
+++ b/pom.xml
@@ -5,7 +5,7 @@
 	<parent>
 		<groupId>org.scijava</groupId>
 		<artifactId>pom-scijava</artifactId>
-		<version>28.0.0</version>
+		<version>29.2.0</version>
 		<relativePath />
 	</parent>
 
@@ -146,13 +146,6 @@
 
 		<!-- NB: Deploy releases to the SciJava Maven repository. -->
 		<releaseProfiles>deploy-to-scijava</releaseProfiles>
-
-		<miglayout-swing.version>5.2</miglayout-swing.version>
-		<scijava-search.version>0.7.0</scijava-search.version>
-		<scijava-ui-awt.version>0.1.7</scijava-ui-awt.version>
-		<scijava-ui-swing.version>0.13.1</scijava-ui-swing.version>
-		<scijava-common.version>2.83.1</scijava-common.version>
-		<parsington.version>2.0.0</parsington.version>
 	</properties>
 
 	<repositories>
diff --git a/src/main/java/org/scijava/ui/swing/script/EditorPane.java b/src/main/java/org/scijava/ui/swing/script/EditorPane.java
index 6825dfde..debea1e9 100644
--- a/src/main/java/org/scijava/ui/swing/script/EditorPane.java
+++ b/src/main/java/org/scijava/ui/swing/script/EditorPane.java
@@ -500,7 +500,7 @@ protected void setLanguage(final ScriptLanguage language,
 		}
 		catch (final NullPointerException exc) {
 			// NB: Avoid possible NPEs in RSyntaxTextArea code.
-			// See: http://fiji.sc/bugzilla/show_bug.cgi?id=1181
+			// See: https://fiji.sc/bug/1181.html
 			log.debug(exc);
 		}
 
diff --git a/src/main/java/org/scijava/ui/swing/script/FileFunctions.java b/src/main/java/org/scijava/ui/swing/script/FileFunctions.java
index 055ced1e..cd6c17b9 100644
--- a/src/main/java/org/scijava/ui/swing/script/FileFunctions.java
+++ b/src/main/java/org/scijava/ui/swing/script/FileFunctions.java
@@ -47,6 +47,7 @@
 import java.util.Iterator;
 import java.util.List;
 import java.util.Map;
+import java.util.Objects;
 import java.util.jar.JarEntry;
 import java.util.jar.JarFile;
 import java.util.regex.Matcher;
@@ -65,7 +66,10 @@
 import org.scijava.util.AppUtils;
 import org.scijava.util.FileUtils;
 import org.scijava.util.LineOutputStream;
+import org.scijava.util.Manifest;
+import org.scijava.util.POM;
 import org.scijava.util.ProcessUtils;
+import org.scijava.util.Types;
 
 /**
  * TODO
@@ -152,9 +156,12 @@ public boolean isBinaryFile(final String path) {
 	}
 
 	/**
-	 * Make a sensible effort to get the path of the source for a class.
+	 * @deprecated Use {@link #getSourceURL(String)} instead.
+	 * @throws ClassNotFoundException
 	 */
-	public String getSourcePath(final String className)
+	@Deprecated
+	public String getSourcePath(
+		@SuppressWarnings("unused") final String className)
 		throws ClassNotFoundException
 	{
 		// move updater's stuff into ij-core and re-use here
@@ -162,7 +169,22 @@ public String getSourcePath(final String className)
 	}
 
 	public String getSourceURL(final String className) {
-		return "http://fiji.sc/" + className.replace('.', '/') + ".java";
+		final Class<?> c = Types.load(className, false);
+		final POM pom = POM.getPOM(c);
+		final String scmPath = pom.getSCMURL();
+		if (scmPath == null || scmPath.isEmpty()) return null;
+		final String branch;
+		final String scmTag = pom.getSCMTag();
+		if (scmTag == null || scmTag.isEmpty() || Objects.equals(scmTag, "HEAD")) {
+			final Manifest m = Manifest.getManifest(c);
+			final String commit = m == null ? null : m.getImplementationBuild();
+			branch = commit == null || commit.isEmpty() ? "master" : commit;
+		}
+		else branch = scmTag;
+
+		final String prefix = scmPath.endsWith("/") ? scmPath : scmPath + "/";
+		return prefix + "blob/" + branch + "/src/main/java/" + //
+			className.replace('.', '/') + ".java";
 	}
 
 	protected static Map<String, List<String>> class2source;
diff --git a/src/main/java/org/scijava/ui/swing/script/TextEditor.java b/src/main/java/org/scijava/ui/swing/script/TextEditor.java
index 6e99c6fc..07323382 100644
--- a/src/main/java/org/scijava/ui/swing/script/TextEditor.java
+++ b/src/main/java/org/scijava/ui/swing/script/TextEditor.java
@@ -162,6 +162,8 @@
 import org.scijava.ui.swing.script.commands.KillScript;
 import org.scijava.util.FileUtils;
 import org.scijava.util.MiscUtils;
+import org.scijava.util.POM;
+import org.scijava.util.Types;
 import org.scijava.widget.FileWidget;
 
 /**
@@ -1402,22 +1404,14 @@ else if (source == openMacroFunctions) try {
 		else if (source == extractSourceJar) extractSourceJar();
 		else if (source == openSourceForClass) {
 			final String className = getSelectedClassNameOrAsk();
-			if (className != null) try {
-				final String path = new FileFunctions(this).getSourcePath(className);
-				if (path != null) open(new File(path));
-				else {
+			if (className != null) {
+				try {
 					final String url = new FileFunctions(this).getSourceURL(className);
-					try {
-						platformService.open(new URL(url));
-					}
-					catch (final Throwable e) {
-						handleException(e);
-					}
+					platformService.open(new URL(url));
+				}
+				catch (final Throwable e) {
+					handleException(e);
 				}
-			}
-			catch (final ClassNotFoundException e) {
-				log.debug(e);
-				error("Could not open source for class " + className);
 			}
 		}
 		/* TODO
@@ -2606,10 +2600,63 @@ public void openHelp(final String className) {
 	 * @param className
 	 * @param withFrames
 	 */
-	public void openHelp(final String className, final boolean withFrames) {
-		if (className == null) {
-			// FIXME: This cannot be right.
-			getSelectedClassNameOrAsk();
+	public void openHelp(String className, final boolean withFrames) {
+		if (className == null) className = getSelectedClassNameOrAsk();
+		if (className == null) return;
+		final Class<?> c = Types.load(className, false);
+
+		final String path = (withFrames ? "index.html?" : "") + //
+				className.replace('.', '/') + ".html";
+
+		final String url;
+
+		if (className.startsWith("java.") || className.startsWith("javax.")) {
+			// Core Java class -- use javadoc.scijava.org/Java<#> link.
+			final String javaVersion = System.getProperty("java.version");
+			final String majorVersion;
+			if (javaVersion.startsWith("1.")) {
+				majorVersion = javaVersion.substring(2, javaVersion.indexOf('.', 2));
+			}
+			else majorVersion = javaVersion.substring(0, javaVersion.indexOf('.'));
+			url = "https://javadoc.scijava.org/Java" + majorVersion + "/" + path;
+		}
+		else {
+			// Third party library -- look for a Maven POM identifying it.
+			final POM pom = POM.getPOM(c);
+			if (pom == null) {
+				throw new IllegalArgumentException(//
+					"Unknown origin for class " + className);
+			}
+			final String releaseProfiles = pom.cdata("//properties/releaseProfiles");
+			final boolean scijavaRepo = "deploy-to-scijava".equals(releaseProfiles);
+			if (scijavaRepo) {
+				// Use javadoc.scijava.org -- try to figure out which project.
+				// Maybe some day, we can bake this information into the POM.
+				final String project;
+				final String g = pom.getGroupId();
+				if ("net.imagej".equals(g)) {
+					project = "ij".equals(pom.getArtifactId()) ? "ImageJ1" : "ImageJ";
+				}
+				else if ("io.scif".equals(g)) project = "SCIFIO";
+				else if ("net.imglib2".equals(g)) project = "ImgLib2";
+				else if ("org.bonej".equals(g)) project = "BoneJ";
+				else if ("org.scijava".equals(g)) project = "SciJava";
+				else if ("sc.fiji".equals(g)) project = "Fiji";
+				else project = "Java";
+				url = "https://javadoc.scijava.org/" + project + "/" + path;
+			}
+			else {
+				// Assume Maven Central -- use javadoc.io.
+				url = "https://javadoc.io/static/" + pom.getGroupId() + "/" + //
+					pom.getArtifactId() + "/" + pom.getVersion() + "/" + path;
+			}
+		}
+
+		try {
+			platformService.open(new URL(url));
+		}
+		catch (final Throwable e) {
+			handleException(e);
 		}
 	}