cmake -E: Add CLI argument spell checking

Use Levenshtein distance to track when the user may have made a typo in
an argument to `cmake -E` (i.e., an invalid mode; mode sub-arguments
are not tracked).
diff --git a/.codespellrc b/.codespellrc
index 85614e5..94fa8e8 100644
--- a/.codespellrc
+++ b/.codespellrc
@@ -16,6 +16,7 @@
   Source/CursesDialog/form/*,
   Source/kwsys/*,
   Tests/RunCMake/CPack/tests/DMG_SLA/German.*,
+  Tests/RunCMake/CommandLine/RunCMakeTest.cmake,
   Tests/RunCMake/ParseImplicitData/*.input,
   Tests/RunCMake/cmake_language/call_typo_function.cmake,
   Tests/StringFileTest/test.utf8,
diff --git a/Source/cmcmd.cxx b/Source/cmcmd.cxx
index 2ef3526..77b5771 100644
--- a/Source/cmcmd.cxx
+++ b/Source/cmcmd.cxx
@@ -2518,6 +2518,26 @@
       }
       return 1;
     }
+
+    std::vector<std::string> availableCommands =
+      CommandLineHelpEntryNames(AvailableCommands);
+#if defined(_WIN32) && !defined(__CYGWIN__)
+    cm::append(availableCommands,
+               CommandLineHelpEntryNames(AvailableWindowsCommands));
+#endif
+    // Some commands above only fall through to here when they were
+    // matched by name but had the wrong number/form of arguments; only
+    // offer a suggestion when the name itself is not recognized.
+    if (std::find(availableCommands.begin(), availableCommands.end(),
+                  args[1]) == availableCommands.end()) {
+      std::string error = cmStrCat("Unknown command \"", args[1], "\".");
+      std::string const suggestion =
+        cmFindClosestString(args[1], availableCommands);
+      if (!suggestion.empty()) {
+        error = cmStrCat(error, " Did you mean \"", suggestion, "\"?");
+      }
+      cmSystemTools::Error(error);
+    }
   }
 
   CMakeCommandUsage(args[0]);
diff --git a/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-result.txt b/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-result.txt
new file mode 100644
index 0000000..d00491f
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-result.txt
@@ -0,0 +1 @@
+1
diff --git a/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-stderr.txt b/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-stderr.txt
new file mode 100644
index 0000000..221fd85
--- /dev/null
+++ b/Tests/RunCMake/CommandLine/E_unknown-command-did-you-mean-stderr.txt
@@ -0,0 +1,4 @@
+^CMake Error: Unknown command "enviroment"\. Did you mean "environment"\?
+CMake Error: cmake version .*
+Usage: .* -E <command> \[arguments\.\.\.\]
+Available commands:
diff --git a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
index efacda3..ad4678c 100644
--- a/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
+++ b/Tests/RunCMake/CommandLine/RunCMakeTest.cmake
@@ -61,6 +61,7 @@
 run_cmake_command(U-no-src ${CMAKE_COMMAND} -B DummyBuildDir -U VAR)
 run_cmake_command(Uno-src ${CMAKE_COMMAND} -B DummyBuildDir -UVAR)
 run_cmake_command(E-no-arg ${CMAKE_COMMAND} -E)
+run_cmake_command(E_unknown-command-did-you-mean ${CMAKE_COMMAND} -E enviroment)
 run_cmake_command(E_capabilities ${CMAKE_COMMAND} -E capabilities)
 run_cmake_command(E_capabilities-arg ${CMAKE_COMMAND} -E capabilities --extra-arg)
 run_cmake_command(E_compare_files-different-eol ${CMAKE_COMMAND} -E compare_files ${RunCMake_SOURCE_DIR}/compare_files/lf ${RunCMake_SOURCE_DIR}/compare_files/crlf)