From 2791a6f18dd2befe6b064d070d66ca08faae64fd Mon Sep 17 00:00:00 2001
From: Girish Palya <girishji@gmail.com>
Date: Sun, 25 Jun 2023 13:26:37 +0200
Subject: [PATCH] Make buffer completion responsive for large files

Since buffer completion processes the current buffer everytime user
types something, it will degrade the experience for large files.

This change adds a timeout to buffer completor function. Processing
current buffer is cut short when timeout is exceeded. Setting
timeout to 0 will revert back to existing behaviour.

Default is set to 100 ms, good for scanning 6000 lines on M1 macbook.

It is possible to get fancy by scanning locality of cursor first
but such complication may not be worth the complexity.

Tested on >20k line files (I have to open these large C files
filled with hw specs occasionally).

M  autoload/lsp/completion.vim
M  autoload/lsp/options.vim
M  doc/lsp.txt
---
 autoload/lsp/completion.vim | 38 ++++++++++++++++++++++---------------
 autoload/lsp/options.vim    |  2 ++
 doc/lsp.txt                 | 13 ++++++++++---
 3 files changed, 35 insertions(+), 18 deletions(-)

diff --git a/autoload/lsp/completion.vim b/autoload/lsp/completion.vim
index 66ea9c5..9b2d598 100644
--- a/autoload/lsp/completion.vim
+++ b/autoload/lsp/completion.vim
@@ -114,22 +114,30 @@ enddef
 
 # add completion from current buf
 def CompletionFromBuffer(items: list<dict<any>>)
-    var words = {}
-    for line in getline(1, '$')
-        for word in line->split('\W\+')
-            if !words->has_key(word) && word->len() > 1
-                words[word] = 1
-                items->add({
-                    label: word,
-                    data: {
-                        entryNames: [word],
-                    },
-                    kind: 26,
-                    documentation: "",
-                })
-            endif
-        endfor
+  var words = {}
+  var start = reltime()
+  var linenr = 1
+  for line in getline(1, '$')
+    for word in line->split('\W\+')
+      if !words->has_key(word) && word->len() > 1
+	words[word] = 1
+	items->add({
+	  label: word,
+	  data: {
+	    entryNames: [word],
+	  },
+	  kind: 26,
+	  documentation: "",
+	})
+      endif
     endfor
+    # Check every 200 lines if timeout is exceeded
+    if opt.lspOptions.bufferCompletionTimeout > 0 && linenr % 200 == 0 && 
+	start->reltime()->reltimefloat() * 1000 > opt.lspOptions.bufferCompletionTimeout
+      break
+    endif
+    linenr += 1
+  endfor
 enddef
 
 # process the 'textDocument/completion' reply from the LSP server
diff --git a/autoload/lsp/options.vim b/autoload/lsp/options.vim
index 8ccf7e4..be55f05 100644
--- a/autoload/lsp/options.vim
+++ b/autoload/lsp/options.vim
@@ -77,6 +77,8 @@ export var lspOptions: dict<any> = {
   useQuickfixForLocations: false,
   # add to autocomplition list current buffer words
   useBufferCompletion: false,
+  # Limit the time autocompletion searches for words in current buffer (in milliseconds)
+  bufferCompletionTimeout: 100,
   # Enable support for custom completion kinds
   customCompletionKinds: false,
   # A dictionary with all completion kinds that you want to customize
diff --git a/doc/lsp.txt b/doc/lsp.txt
index 910dcd1..3c212fb 100644
--- a/doc/lsp.txt
+++ b/doc/lsp.txt
@@ -589,10 +589,17 @@ useQuickfixForLocations	|Boolean| option.  Show |:LspShowReferences| in a
 						*lsp-opt-useBufferCompletion*
 useBufferCompletion     |Boolean| option. If enabled, the words from the
 			current buffer are added to the auto completion list.
-			This may degrade Vim performance as the current buffer
-			contents are processed every time the completion menu
-			is displayed.
 			By default this is set to false.
+						*lsp-opt-bufferCompletionTimeout*
+bufferCompletionTimeout |Number| option. Specifies how long (in milliseconds) 
+			to wait while processing current buffer for 
+			autocompletion words.  If set too high Vim performance
+			may degrade as the current buffer contents are
+			processed every time the completion menu is displayed.
+			If set to 0 the entire buffer is processed without
+			regard to timeout.
+			By default this is set to 100 ms (good for scanning
+			a file of about 6000 lines on M1 Macbook).
 
 For example, to disable the automatic placement of signs for the LSP
 diagnostic messages, you can add the following line to your .vimrc file: >
-- 
2.51.0