Repository: intellij-rust/intellij-rust
Issue: IntelliJ Projector can't open semver docs.rs/.../^...

## Environment

* **IntelliJ Rust plugin version:** master 4e2c22f399e0431c9cf30bfc24d6dd1b2a1256e6
* **Rust toolchain version:** N/A
* **IDE name and version:** CLion
* **Operating system:** Ubuntu 21.04

## Problem description
IntelliJ Projector runs an IDE (CLion in my case) locally, and allows a remote machine to control it via a web browser.

[Full logs](https://gist.github.com/punkeel/67d3065caac9b2318576557207ca3e84). Snippet for SEO:
```
2021-05-15 00:57:24,724 [ 512088]  ERROR - .intellij.openapi.command.impl - Current Command:  
2021-05-15 00:57:42,610 [ 529974]  ERROR - .intellij.openapi.command.impl - Illegal character in path at index 20: https://docs.rs/csv/^1 
java.net.URISyntaxException: Illegal character in path at index 20: https://docs.rs/csv/^1
	at java.base/java.net.URI$Parser.fail(URI.java:2913)
	at com.intellij.ide.BrowserUtil.browse(BrowserUtil.java)
	at org.rust.toml.CargoCrateDocLineMarkerProvider$genLineMarkerInfo$1.navigate(CargoCrateDocLineMarkerProvider.kt:60)
	at com.intellij.codeInsight.daemon.NavigateAction.actionPerformed(NavigateAction.java:62)
	at com.intellij.openapi.actionSystem.ex.ActionUtil.lambda$performActionDumbAware$5(ActionUtil.java:273)
2021-05-15 00:57:42,611 [ 529975]  ERROR - .intellij.openapi.command.impl - CLion 2021.1.1  Build #CL-211.7142.21 
2021-05-15 00:57:42,611 [ 529975]  ERROR - .intellij.openapi.command.impl - JDK: 11.0.10; VM: Dynamic Code Evolution 64-Bit Server VM; Vendor: JetBrains s.r.o. 
2021-05-15 00:57:42,611 [ 529975]  ERROR - .intellij.openapi.command.impl - OS: Linux 
2021-05-15 00:57:42,612 [ 529976]  ERROR - .intellij.openapi.command.impl - Plugin to blame: Rust version: 0.3.146.3826-211 
```

## Steps to reproduce
1. Install Projector, CLion and the Rust plugin
2. Create a Rust project. Add a dependency, eg `csv = "1"`
3. From the projector client (web or native), open Cargo.toml and try to click the rust-docs icon next to `csv = "1"`. An error message appears.

Changed files:
toml/src/test/kotlin/org/rust/toml/CargoCrateDocLineMarkerProviderTest.kt, toml/src/main/kotlin/org/rust/toml/CargoCrateDocLineMarkerProvider.kt

diff --git a/toml/src/main/kotlin/org/rust/toml/CargoCrateDocLineMarkerProvider.kt b/toml/src/main/kotlin/org/rust/toml/CargoCrateDocLineMarkerProvider.kt
index 5bbd9c458..7e3e2ff0b 100644
--- a/toml/src/main/kotlin/org/rust/toml/CargoCrateDocLineMarkerProvider.kt
+++ b/toml/src/main/kotlin/org/rust/toml/CargoCrateDocLineMarkerProvider.kt
@@ -10,6 +10,7 @@ import com.intellij.codeInsight.daemon.LineMarkerProvider
 import com.intellij.ide.BrowserUtil
 import com.intellij.openapi.editor.markup.GutterIconRenderer
 import com.intellij.psi.PsiElement
+import com.intellij.util.io.URLUtil
 import org.rust.cargo.CargoConstants
 import org.rust.ide.icons.RsIcons
 import org.rust.ide.lineMarkers.RsLineMarkerInfoUtils
@@ -57,7 +58,7 @@ class CargoCrateDocLineMarkerProvider : LineMarkerProvider {
             anchor,
             anchor.textRange,
             RsIcons.DOCS_MARK,
-            { _, _ -> BrowserUtil.browse("https://docs.rs/$name/$urlVersion") },
+            { _, _ -> BrowserUtil.browse("https://docs.rs/$name/${URLUtil.encodeURIComponent(urlVersion)}") },
             GutterIconRenderer.Alignment.LEFT
         ) { "Open documentation for `$name@$urlVersion`" }
     }
diff --git a/toml/src/test/kotlin/org/rust/toml/CargoCrateDocLineMarkerProviderTest.kt b/toml/src/test/kotlin/org/rust/toml/CargoCrateDocLineMarkerProviderTest.kt
index 843f588fc..eea2efb9d 100644
--- a/toml/src/test/kotlin/org/rust/toml/CargoCrateDocLineMarkerProviderTest.kt
+++ b/toml/src/test/kotlin/org/rust/toml/CargoCrateDocLineMarkerProviderTest.kt
@@ -22,32 +22,32 @@ class CargoCrateDocLineMarkerProviderTest : CargoTomlLineMarkerProviderTestBase(
 
         [target.'cfg(unix)'.dependencies]
         base64 = "0.8.0"  # - Open documentation for `base64@^0.8.0`
-    """, "https://docs.rs/base64/^0.8.0", "https://docs.rs/base64/^0.8.0")
+    """, "https://docs.rs/base64/%5E0.8.0", "https://docs.rs/base64/%5E0.8.0")
 
     fun `test platform specific`() = doTest("""
         [target.'cfg(unix)'.dependencies]
         base64 = "0.8.0"  # - Open documentation for `base64@^0.8.0`
-    """, "https://docs.rs/base64/^0.8.0")
+    """, "https://docs.rs/base64/%5E0.8.0")
 
     fun `test standard detailed`() = doTest("""
         [dependencies]
         jquery = { version = "1.0.2", optional = true }  # - Open documentation for `jquery@^1.0.2`
-    """, "https://docs.rs/jquery/^1.0.2")
+    """, "https://docs.rs/jquery/%5E1.0.2")
 
     fun `test standard dev`() = doTest("""
         [dev-dependencies]
         libc = "0.2.33"  # - Open documentation for `libc@^0.2.33`
-    """, "https://docs.rs/libc/^0.2.33")
+    """, "https://docs.rs/libc/%5E0.2.33")
 
     fun `test standard build`() = doTest("""
         [build-dependencies]
         libc = "0.2.33"  # - Open documentation for `libc@^0.2.33`
-    """, "https://docs.rs/libc/^0.2.33")
+    """, "https://docs.rs/libc/%5E0.2.33")
 
     fun `test specific crate`() = doTest("""
         [dependencies.clap]  # - Open documentation for `clap@^2.27.1`
         version = "2.27.1"
-    """, "https://docs.rs/clap/^2.27.1")
+    """, "https://docs.rs/clap/%5E2.27.1")
 
     fun `test no link to doc`() = doTest("""
         [dependencies]
@@ -57,7 +57,7 @@ class CargoCrateDocLineMarkerProviderTest : CargoTomlLineMarkerProviderTestBase(
     fun `test renamed dependencies`() = doTest("""
         [dependencies]
         config_rs = { package = "config", version = "0.9" }  # - Open documentation for `config@^0.9`
-    """, "https://docs.rs/config/^0.9")
+    """, "https://docs.rs/config/%5E0.9")
 
     fun `test unescape string literals`() = doTest("""
         [dependencies]
@@ -65,10 +65,10 @@ class CargoCrateDocLineMarkerProviderTest : CargoTomlLineMarkerProviderTestBase(
         serde_json = { version = '''1.0.104''' }  # - Open documentation for `serde_json@^1.0.104`
         serde_yaml_rs = { package = 'serde_yaml', version = '''0.8.11''' }  # - Open documentation for `serde_yaml@^0.8.11`
         serde_derive_rs = { package = "serde\\u005Fderive", version ="1\\u002E0\\u002E104" }  # - Open documentation for `serde_derive@^1.0.104`
-    """, "https://docs.rs/serde/^1.0.104",
-        "https://docs.rs/serde_json/^1.0.104",
-        "https://docs.rs/serde_yaml/^0.8.11",
-        "https://docs.rs/serde_derive/^1.0.104"
+    """, "https://docs.rs/serde/%5E1.0.104",
+        "https://docs.rs/serde_json/%5E1.0.104",
+        "https://docs.rs/serde_yaml/%5E0.8.11",
+        "https://docs.rs/serde_derive/%5E1.0.104"
     )
 
     fun `test empty version`() = doTest("""
@@ -79,7 +79,7 @@ class CargoCrateDocLineMarkerProviderTest : CargoTomlLineMarkerProviderTestBase(
     fun `test exact version`() = doTest("""
         [dependencies]
         base64 = "=0.8.0"  # - Open documentation for `base64@=0.8.0`
-    """, "https://docs.rs/base64/=0.8.0")
+    """, "https://docs.rs/base64/%3D0.8.0")
 
     private fun doTest(@Language("Toml") source: String, vararg expectedUrls: String) {
         doTestByText(source)