Repository: hannah-sten/texify-idea
Issue: The selected directory is not a valid home for MiKTeX Windows SDK

#### Type of JetBrains IDE (IntelliJ, PyCharm, etc.) and version
Intellij IDEA 2021.2.1 Build #IU-212.5080.55

#### Operating System 
Windows 10

#### TeXiFy IDEA version
0.7.11

#### What I did (steps to reproduce)
New Project > LaTeX > Add SDK > "C:\\Program Files\\MiKTeX"

I can't choose the SDK Home Directory because it says it's not valid. I have tried all of the subdirectories from MiKTeX to MiKTeX\\miktex\\bin\\x64\\, none of them work.

Output of my `pdftex --version`:
```
> pdftex --version
MiKTeX-pdfTeX 4.8 (MiKTeX 21.10)
© 1982 D. E. Knuth, © 1996-2021 Hàn Thế Thành
TeX is a trademark of the American Mathematical Society.
using bzip2 version 1.0.8, 13-Jul-2019
compiled with curl version 7.72.0; using libcurl/7.72.0 Schannel
compiled with expat version 2.2.10; using expat_2.2.10
compiled with jpeg version 9.4
compiled with liblzma version 50020052; using 50020052
compiled with libpng version 1.6.37; using 1.6.37
compiled with libressl version LibreSSL 3.1.4; using LibreSSL 3.1.4
compiled with MiKTeX Application Framework version 4.2.1; using 4.2.1
compiled with MiKTeX Core version 4.9; using 4.9
compiled with MiKTeX Archive Extractor version 4.0; using 4.0
compiled with MiKTeX Package Manager version 4.5; using 4.5
compiled with uriparser version 0.9.4
compiled with xpdf version 4.02
compiled with zlib version 1.2.11; using 1.2.11
```


Changed files:
src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt, src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt, src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt, src/nl/hannahsten/texifyidea/settings/sdk/NativeTexliveSdk.kt, src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt, src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt, src/nl/hannahsten/texifyidea/util/Strings.kt

diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt
index efb558a10..9cef2e653 100644
--- a/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt
+++ b/src/nl/hannahsten/texifyidea/settings/sdk/LatexSdkUtil.kt
@@ -1,6 +1,9 @@
 package nl.hannahsten.texifyidea.settings.sdk
 
+import com.intellij.notification.Notification
+import com.intellij.notification.NotificationType
 import com.intellij.openapi.project.Project
+import com.intellij.openapi.project.ProjectManager
 import com.intellij.openapi.projectRoots.ProjectJdkTable
 import com.intellij.openapi.projectRoots.Sdk
 import com.intellij.openapi.roots.OrderRootType
@@ -10,6 +13,7 @@ import com.intellij.openapi.vfs.VirtualFile
 import nl.hannahsten.texifyidea.run.latex.LatexDistributionType
 import nl.hannahsten.texifyidea.util.getLatexRunConfigurations
 import nl.hannahsten.texifyidea.util.runCommand
+import nl.hannahsten.texifyidea.util.runCommandWithExitCode
 import java.io.File
 
 /**
@@ -81,6 +85,33 @@ object LatexSdkUtil {
      */
     fun getPdflatexParentPath(homePath: String) = File("$homePath/bin").listFiles()?.firstOrNull()?.path
 
+    /**
+     * Run pdflatex in the given directory and check if it is present and valid.
+     * If not, also throw a notification in the currently open project (if any), because we cannot modify the actual message in the dialog which complains when an SDK is not valid.
+     *
+     * @param errorMessage Message to show to the user when directory is null. If this is null, no notification will be shown.
+     */
+    fun isPdflatexPresent(directory: String?, errorMessage: String?, sdkName: String): Boolean {
+        val output = if (directory == null) {
+            errorMessage
+        }
+        else {
+            // .exe is optional on windows
+            runCommandWithExitCode("$directory${File.separator}pdflatex", "--version", returnExceptionMessage = true).first
+        } ?: "No output given by $directory${File.separator}pdflatex --version"
+
+        if (output.contains("pdfTeX")) return true
+
+        // Don't send notification if there is no message on purpose
+        if (errorMessage == null) return false
+
+        // Show notification to explain the reason why the SDK is not valid, but only if any project is open
+        // We have to do this because we can't customize the popup which says the SDK is not valid
+        val project = ProjectManager.getInstance().openProjects.firstOrNull { !it.isDisposed } ?: return false
+        Notification("LaTeX", "Invalid $sdkName home directory", output, NotificationType.WARNING).notify(project)
+        return false
+    }
+
     /**
      * Find the full name of the distribution in use, e.g. TeX Live 2019.
      */
diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt
index 8607a1ba7..12014eb1b 100644
--- a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt
+++ b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexLinuxSdk.kt
@@ -5,7 +5,6 @@ import com.intellij.openapi.vfs.LocalFileSystem
 import com.intellij.openapi.vfs.VirtualFile
 import nl.hannahsten.texifyidea.run.latex.LatexDistributionType
 import nl.hannahsten.texifyidea.util.runCommand
-import java.io.File
 import java.nio.file.Paths
 
 /**
@@ -39,12 +38,13 @@ class MiktexLinuxSdk : LatexSdk("MiKTeX Mac/Linux SDK") {
         return listOf(
             Paths.get(System.getProperty("user.home"), "bin").toString(),
             "/usr/local/bin"
-        ).filter { isValidSdkHome(it) }.toMutableList()
+        ).filter { LatexSdkUtil.isPdflatexPresent(it, null, name) }.toMutableList()
     }
 
     override fun isValidSdkHome(path: String): Boolean {
         // We just want a path where pdflatex is present
-        return "$path${File.separator}pdflatex --version".runCommand()?.contains("pdfTeX") == true
+        val errorMessage = "Could not find $path/pdflatex"
+        return LatexSdkUtil.isPdflatexPresent(path, errorMessage, name)
     }
 
     override fun getVersionString(sdk: Sdk): String? {
diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt
index a2c48af06..6b700fea3 100644
--- a/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt
+++ b/src/nl/hannahsten/texifyidea/settings/sdk/MiktexWindowsSdk.kt
@@ -35,7 +35,7 @@ class MiktexWindowsSdk : LatexSdk("MiKTeX Windows SDK") {
         val paths = "where pdflatex".runCommand()
         if (paths != null && !paths.contains("Could not find")) { // Full output is INFO: Could not find files for the given pattern(s).
             paths.split("\\r\\n").forEach { path ->
-                val index = path.findLastAnyOf(setOf("miktex\\\\bin"))?.first ?: path.length - 1
+                val index = path.findLastAnyOf(setOf("miktex\\\\bin"))?.first ?: (path.length - 1)
                 results.add(path.substring(0, index))
             }
         }
@@ -58,8 +58,9 @@ class MiktexWindowsSdk : LatexSdk("MiKTeX Windows SDK") {
     override fun isValidSdkHome(path: String): Boolean {
         // We want the MiKTeX 2.9 folder to be selected
         // Assume path is of the form C:\\Users\\username\\AppData\\Local\\Programs\\MiKTeX 2.9\\miktex\\bin\\x64\\pdflatex.exe
-        val parent = LatexSdkUtil.getPdflatexParentPath(Paths.get(path, "miktex").toString())
-        return "\\"$parent\\\\pdflatex\\" --version".runCommand()?.contains("pdfTeX") == true
+        val directory = LatexSdkUtil.getPdflatexParentPath(Paths.get(path, "miktex").toString())
+        val errorMessage = "Could not find $path/miktex/bin/*/pdflatex, please make sure you selected the MiKTeX installation directory."
+        return LatexSdkUtil.isPdflatexPresent(directory, errorMessage, name)
     }
 
     override fun getVersionString(sdk: Sdk): String? {
diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/NativeTexliveSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/NativeTexliveSdk.kt
index af407fa42..edbc65507 100644
--- a/src/nl/hannahsten/texifyidea/settings/sdk/NativeTexliveSdk.kt
+++ b/src/nl/hannahsten/texifyidea/settings/sdk/NativeTexliveSdk.kt
@@ -47,13 +47,14 @@ class NativeTexliveSdk : TexliveSdk("Native TeX Live SDK") {
         // This is different from a TexliveSdk installation, where we have the parent directory of the TeX Live installation and find everything there.
 
         // If this is a valid LaTeX installation, pdflatex should be present
-        return "$path/pdflatex --version".runCommand()?.contains("pdfTeX") == true
+        val errorMessage = "Could not find $path/pdflatex"
+        return LatexSdkUtil.isPdflatexPresent(path, errorMessage, name)
     }
 
     override fun getVersionString(sdkHome: String?): String {
         // Assume pdflatex --version contains output of the form
         // pdfTeX 3.14159265-2.6-1.40.21 (TeX Live 2020/mydistro)
-        return """TeX Live (\\d\\d\\d\\d\\/.+)""".toRegex().find(LatexSdkUtil.pdflatexVersionText)?.value ?: "Unknown version"
+        return """TeX Live (\\d\\d\\d\\d/.+)""".toRegex().find(LatexSdkUtil.pdflatexVersionText)?.value ?: "Unknown version"
     }
 
     override fun getDefaultDocumentationUrl(sdk: Sdk): String {
diff --git a/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt b/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt
index 421f49a83..25e39713a 100644
--- a/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt
+++ b/src/nl/hannahsten/texifyidea/settings/sdk/TexliveSdk.kt
@@ -80,7 +80,9 @@ open class TexliveSdk(name: String = "TeX Live SDK") : LatexSdk(name) {
 
         // If this is a valid LaTeX installation, pdflatex should be present in a subfolder in bin, e.g. $path/bin/x86_64-linux/pdflatex
         val parent = LatexSdkUtil.getPdflatexParentPath(path)
-        return "$parent/pdflatex --version".runCommand()?.contains("pdfTeX") == true
+
+        val errorMessage = "Could not find $path/bin/*/pdflatex"
+        return LatexSdkUtil.isPdflatexPresent(parent, errorMessage, name)
     }
 
     override fun getLatexDistributionType() = LatexDistributionType.TEXLIVE
diff --git a/src/nl/hannahsten/texifyidea/util/Strings.kt b/src/nl/hannahsten/texifyidea/util/Strings.kt
index 17d14fdca..3e9d3351a 100644
--- a/src/nl/hannahsten/texifyidea/util/Strings.kt
+++ b/src/nl/hannahsten/texifyidea/util/Strings.kt
@@ -195,6 +195,7 @@ fun String.removeHtmlTags() = this.replace(PatternMagic.htmlTag.toRegex(), "")
 
 /**
  * Run a command in the terminal.
+ * You can only use this if you are sure you don't have paths and other escaped things with spaces.
  *
  * @return The output of the command or null if an exception was thrown.
  */
diff --git a/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt b/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt
index 16f4cfe35..b424b326a 100644
--- a/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt
+++ b/src/nl/hannahsten/texifyidea/util/SystemEnvironment.kt
@@ -51,8 +51,10 @@ fun runCommand(vararg commands: String, workingDirectory: File? = null): String?
 
 /**
  * See [runCommand], but also returns exit code.
+ *
+ * @param returnExceptionMessage Whether to return exception messages if exceptions are thrown.
  */
-fun runCommandWithExitCode(vararg commands: String, workingDirectory: File? = null, timeout: Long = 3): Pair<String?, Int> {
+fun runCommandWithExitCode(vararg commands: String, workingDirectory: File? = null, timeout: Long = 3, returnExceptionMessage: Boolean = false): Pair<String?, Int> {
     return try {
         val proc = GeneralCommandLine(*commands)
             .withParentEnvironmentType(GeneralCommandLine.ParentEnvironmentType.CONSOLE)
@@ -71,10 +73,20 @@ fun runCommandWithExitCode(vararg commands: String, workingDirectory: File? = nu
         }
     }
     catch (e: IOException) {
-        Pair(null, -1) // Don't print the stacktrace because that is confusing.
+        if (!returnExceptionMessage) {
+            Pair(null, -1) // Don't print the stacktrace because that is confusing.
+        }
+        else {
+            Pair(e.message, -1)
+        }
     }
     catch (e: ProcessNotCreatedException) {
         // e.g. if the command is just trying if a program can be run or not, and it's not the case
-        Pair(null, -1)
+        if (!returnExceptionMessage) {
+            Pair(null, -1)
+        }
+        else {
+            Pair(e.message, -1)
+        }
     }
 }