Repository: intellij-rust/intellij-rust
Issue: Cannot create a new project from a template when URL contains trailing whitespace

## Environment

* **IntelliJ Rust plugin version:** 0.4.186.5089-223-nightly
* **Rust toolchain version:** 1.66.0 (69f9c33d7 2022-12-12) x86_64-apple-darwin
* **IDE name and version:** CLion 2022.3.1 (CL-223.8214.51)
* **Operating system:** macOS 11.1

## Problem description

Cannot create a new project from a template when URL contains trailing whitespace.


## Steps to reproduce

1. Open New Project dialog
2. Add project template via `+` button with a template URL containing trailing whitespace (e.g. `https://github.com/intellij-rust/wasm-pack-template `)
3. Create project using that template
4. The generated project is empty, and the plugin throws `org.rust.openapiext.RsProcessExecutionException$ProcessAborted` exception


Changed files:
src/main/kotlin/org/rust/openapiext/ui.kt, src/main/kotlin/org/rust/ide/newProject/ui/AddUserTemplateDialog.kt, src/main/kotlin/org/rust/ide/actions/ui/CargoNewCrateDialog.kt

diff --git a/src/main/kotlin/org/rust/ide/actions/ui/CargoNewCrateDialog.kt b/src/main/kotlin/org/rust/ide/actions/ui/CargoNewCrateDialog.kt
index 533a5e25c..bfe5c5a63 100644
--- a/src/main/kotlin/org/rust/ide/actions/ui/CargoNewCrateDialog.kt
+++ b/src/main/kotlin/org/rust/ide/actions/ui/CargoNewCrateDialog.kt
@@ -15,6 +15,7 @@ import com.intellij.ui.dsl.builder.panel
 import org.jetbrains.annotations.TestOnly
 import org.rust.ide.newProject.RsPackageNameValidator
 import org.rust.openapiext.isUnitTestMode
+import org.rust.openapiext.trimmedText
 import javax.swing.JComponent
 
 data class CargoNewCrateSettings(val binary: Boolean, val crateName: String)
@@ -51,7 +52,7 @@ class CargoNewCrateDialog(project: Project, private val root: VirtualFile) : Dia
     private val name = JBTextField(20)
 
     val binary get() = this.typeCombobox.selectedIndex == 0
-    val crateName get() = name.text.trim()
+    val crateName get() = name.trimmedText
 
     init {
         title = "New Cargo Crate"
diff --git a/src/main/kotlin/org/rust/ide/newProject/ui/AddUserTemplateDialog.kt b/src/main/kotlin/org/rust/ide/newProject/ui/AddUserTemplateDialog.kt
index 3564dd521..3310e8d83 100644
--- a/src/main/kotlin/org/rust/ide/newProject/ui/AddUserTemplateDialog.kt
+++ b/src/main/kotlin/org/rust/ide/newProject/ui/AddUserTemplateDialog.kt
@@ -13,8 +13,10 @@ import org.rust.ide.newProject.state.RsUserTemplate
 import org.rust.ide.newProject.state.RsUserTemplatesState
 import org.rust.openapiext.addTextChangeListener
 import org.rust.openapiext.fullWidthCell
+import org.rust.openapiext.trimmedText
 import javax.swing.JComponent
 import javax.swing.event.DocumentEvent
+import javax.swing.event.DocumentEvent.EventType
 
 class AddUserTemplateDialog : DialogWrapper(null) {
     private val repoUrlField: JBTextField = JBTextField().apply {
@@ -43,25 +45,32 @@ class AddUserTemplateDialog : DialogWrapper(null) {
 
     override fun doOKAction() {
         // TODO: Find a better way to handle dialog form validation
-        if (nameField.text.isBlank()) return
-        if (RsUserTemplatesState.getInstance().templates.any { it.name == nameField.text }) return
+        val name = nameField.trimmedText
+        val repoUrl = repoUrlField.trimmedText
+
+        if (name.isBlank()) return
+        if (RsUserTemplatesState.getInstance().templates.any { it.name == name }) return
 
         RsUserTemplatesState.getInstance().templates.add(
-            RsUserTemplate(nameField.text, repoUrlField.text)
+            RsUserTemplate(name, repoUrl)
         )
 
         super.doOKAction()
     }
 
     private fun suggestName(event: DocumentEvent) {
-        if (nameField.text.isNotBlank()) return
-        if (event.length != repoUrlField.text.length) return
-        if (KNOWN_URL_PREFIXES.none { repoUrlField.text.startsWith(it) }) return
+        // Suggest name only if the whole URL was inserted
+        if (event.type == EventType.INSERT && event.length == event.document.length) {
+            if (nameField.text.isNotBlank()) return
+
+            val repoUrl = repoUrlField.trimmedText
+            if (KNOWN_URL_PREFIXES.none { repoUrl.startsWith(it) }) return
 
-        nameField.text = repoUrlField.text
-            .removeSuffix("/")
-            .removeSuffix(".git")
-            .substringAfterLast("/")
+            nameField.text = repoUrl
+                .removeSuffix("/")
+                .removeSuffix(".git")
+                .substringAfterLast("/")
+        }
     }
 
     companion object {
diff --git a/src/main/kotlin/org/rust/openapiext/ui.kt b/src/main/kotlin/org/rust/openapiext/ui.kt
index 9c1eff8c0..77ae7ce68 100644
--- a/src/main/kotlin/org/rust/openapiext/ui.kt
+++ b/src/main/kotlin/org/rust/openapiext/ui.kt
@@ -19,6 +19,7 @@ import com.intellij.openapi.ui.TextFieldWithBrowseButton
 import com.intellij.openapi.util.Disposer
 import com.intellij.openapi.util.NlsContexts.DialogTitle
 import com.intellij.ui.DocumentAdapter
+import com.intellij.ui.components.JBTextField
 import com.intellij.ui.dsl.builder.Cell
 import com.intellij.ui.dsl.builder.Row
 import com.intellij.ui.dsl.gridLayout.HorizontalAlign
@@ -117,3 +118,6 @@ fun <T : JComponent> Row.fullWidthCell(component: T): Cell<T> {
     return cell(component)
         .horizontalAlign(HorizontalAlign.FILL)
 }
+
+val JBTextField.trimmedText: String
+    get() = text.trim()