From d61bf578f3ab10b20ad836901ae73c7424e4f3a6 Mon Sep 17 00:00:00 2001
From: Fangrui Song <i@maskray.me>
Date: Sun, 6 Oct 2019 23:54:45 -0700
Subject: [PATCH] Add initialization option completion.placeholder; change
 client.snippetSupport: false to drop ( and <

* client.snippetSupport: false => `foo`
* client.snippetSupport: true
  + completion.placeholder: false => `foo($1)$0` `bar<$1>()$0`
  + completion.placeholder: true => `foo(${1:int a}, ${2:int b})$0` `bar<${1:typename T}>()$0`

Note, client.snippetSupport is always false if the client does not support snippets.

Close #412
---
 src/config.hh                           | 16 ++++++++++++----
 src/messages/initialize.cc              |  3 +++
 src/messages/textDocument_completion.cc | 23 +++++++++++++++++++----
 3 files changed, 34 insertions(+), 8 deletions(-)

diff --git a/src/config.hh b/src/config.hh
index 2bb30367..d986fe39 100644
--- a/src/config.hh
+++ b/src/config.hh
@@ -129,6 +129,8 @@ struct Config {
     bool hierarchicalDocumentSymbolSupport = true;
     // TextDocumentClientCapabilities.definition.linkSupport
     bool linkSupport = true;
+
+    // If false, disable snippets and complete just the identifier part.
     // TextDocumentClientCapabilities.completion.completionItem.snippetSupport
     bool snippetSupport = true;
   } client;
@@ -176,9 +178,6 @@ struct Config {
     // that implement their own filtering and sorting logic.
     bool filterAndSort = true;
 
-    // Maxmum number of results.
-    int maxNum = 100;
-
     struct Include {
       // Regex patterns to match include completion candidates against. They
       // receive the absolute file path.
@@ -200,6 +199,15 @@ struct Config {
 
       std::vector<std::string> whitelist;
     } include;
+
+    // Maxmum number of results.
+    int maxNum = 100;
+
+    // Add placeholder text. Effective only if client.snippetSupport is true.
+    //
+    // false: foo($1)$0
+    // true: foo(${1:int a}, ${2:int b})$0
+    bool placeholder = true;
   } completion;
 
   struct Diagnostics {
@@ -343,7 +351,7 @@ REFLECT_STRUCT(Config::Completion::Include, blacklist, maxPathSize,
                suffixWhitelist, whitelist);
 REFLECT_STRUCT(Config::Completion, caseSensitivity, detailedLabel,
                dropOldRequests, duplicateOptional, filterAndSort, include,
-               maxNum);
+               maxNum, placeholder);
 REFLECT_STRUCT(Config::Diagnostics, blacklist, onChange, onOpen, onSave,
                spellChecking, whitelist)
 REFLECT_STRUCT(Config::Highlight, largeFileSize, lsRanges, blacklist, whitelist)
diff --git a/src/messages/initialize.cc b/src/messages/initialize.cc
index cf824ae5..bc820178 100644
--- a/src/messages/initialize.cc
+++ b/src/messages/initialize.cc
@@ -319,6 +319,9 @@ void do_initialize(MessageHandler *m, InitializeParam &param,
   didChangeWatchedFiles =
       capabilities.workspace.didChangeWatchedFiles.dynamicRegistration;
 
+  if (!g_config->client.snippetSupport)
+    g_config->completion.duplicateOptional = false;
+
   // Ensure there is a resource directory.
   if (g_config->clang.resourceDir.empty())
     g_config->clang.resourceDir = getDefaultResourceDirectory();
diff --git a/src/messages/textDocument_completion.cc b/src/messages/textDocument_completion.cc
index 866198e0..220a63e1 100644
--- a/src/messages/textDocument_completion.cc
+++ b/src/messages/textDocument_completion.cc
@@ -23,6 +23,7 @@ limitations under the License.
 
 #include <clang/Sema/CodeCompleteConsumer.h>
 #include <clang/Sema/Sema.h>
+#include <llvm/ADT/Twine.h>
 
 #if LLVM_VERSION_MAJOR < 8
 #include <regex>
@@ -382,7 +383,7 @@ void buildItem(const CodeCompletionResult &r, const CodeCompletionString &ccs,
           continue;
         }
         out[i].textEdit.newText +=
-            "${" + std::to_string(out[i].parameters_.size()) + ":" + text + "}";
+            ("${" + Twine(out[i].parameters_.size()) + ":" + text + "}").str();
         out[i].insertTextFormat = InsertTextFormat::Snippet;
       } else if (kind != CodeCompletionString::CK_Informative) {
         out[i].textEdit.newText += text;
@@ -462,9 +463,23 @@ public:
       buildItem(r, *ccs, ls_items);
 
       for (size_t j = first_idx; j < ls_items.size(); j++) {
-        if (g_config->client.snippetSupport &&
-            ls_items[j].insertTextFormat == InsertTextFormat::Snippet)
-          ls_items[j].textEdit.newText += "$0";
+        std::string &s = ls_items[j].textEdit.newText;
+        if (!g_config->client.snippetSupport) {
+          if (s.size()) {
+            // Delete non-identifier parts.
+            if (s.back() == '(' || s.back() == '<')
+              s.pop_back();
+            else if (s.size() >= 2 && !s.compare(s.size() - 2, 2, "()"))
+              s.resize(s.size() - 2);
+          }
+        } else if (ls_items[j].insertTextFormat == InsertTextFormat::Snippet) {
+          if (!g_config->completion.placeholder) {
+            // foo(${1:int a}, ${2:int b}) -> foo($1)$0
+            auto p = s.find("${"), q = s.rfind('}');
+            s.replace(p, q - p + 1, "$1");
+          }
+          s += "$0";
+        }
         ls_items[j].priority_ = ccs->getPriority();
         if (!g_config->completion.detailedLabel) {
           ls_items[j].detail = ls_items[j].label;