From b2af8f153e14f9dd9d39724c47c0f5dc8528e548 Mon Sep 17 00:00:00 2001
From: Filipp Vakhitov <filipp.vakhitov@jetbrains.com>
Date: Sun, 2 Jun 2024 20:48:05 +0300
Subject: [PATCH] Add VimScriptFunctionServiceBase

---
 .../vim/vimscript/services/FunctionStorage.kt | 167 +----------------
 .../vim/api/VimScriptFunctionServiceBase.kt   | 172 ++++++++++++++++++
 2 files changed, 176 insertions(+), 163 deletions(-)
 create mode 100644 vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimScriptFunctionServiceBase.kt

diff --git a/src/main/java/com/maddyhome/idea/vim/vimscript/services/FunctionStorage.kt b/src/main/java/com/maddyhome/idea/vim/vimscript/services/FunctionStorage.kt
index ad04d82e4..75ce2b743 100644
--- a/src/main/java/com/maddyhome/idea/vim/vimscript/services/FunctionStorage.kt
+++ b/src/main/java/com/maddyhome/idea/vim/vimscript/services/FunctionStorage.kt
@@ -8,170 +8,11 @@
 
 package com.maddyhome.idea.vim.vimscript.services
 
-import com.intellij.openapi.diagnostic.logger
-import com.maddyhome.idea.vim.api.VimscriptFunctionService
-import com.maddyhome.idea.vim.ex.ExException
-import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
-import com.maddyhome.idea.vim.vimscript.model.Script
-import com.maddyhome.idea.vim.vimscript.model.VimLContext
-import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
-import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
+import com.maddyhome.idea.vim.api.VimScriptFunctionServiceBase
 import com.maddyhome.idea.vim.vimscript.model.functions.EngineFunctionProvider
-import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
 import com.maddyhome.idea.vim.vimscript.model.functions.IntellijFunctionProvider
-import com.maddyhome.idea.vim.vimscript.model.functions.LazyVimscriptFunction
-import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
+import com.maddyhome.idea.vim.vimscript.model.functions.VimscriptFunctionProvider
 
-internal class FunctionStorage : VimscriptFunctionService {
-
-  private val logger = logger<FunctionStorage>()
-
-  private val globalFunctions: MutableMap<String, FunctionDeclaration> = mutableMapOf()
-
-  private val builtInFunctions: MutableMap<String, LazyVimscriptFunction> = mutableMapOf()
-
-  override fun deleteFunction(name: String, scope: Scope?, vimContext: VimLContext) {
-    if (name[0].isLowerCase() && scope != Scope.SCRIPT_VARIABLE) {
-      throw ExException("E128: Function name must start with a capital or \"s:\": $name")
-    }
-
-    if (scope != null) {
-      when (scope) {
-        Scope.GLOBAL_VARIABLE -> {
-          if (globalFunctions.containsKey(name)) {
-            globalFunctions[name]!!.isDeleted = true
-            globalFunctions.remove(name)
-            return
-          } else {
-            throw ExException("E130: Unknown function: ${scope.c}:$name")
-          }
-        }
-        Scope.SCRIPT_VARIABLE -> {
-          if (vimContext.getFirstParentContext() !is Script) {
-            throw ExException("E81: Using <SID> not in a script context")
-          }
-
-          if (getScriptFunction(name, vimContext) != null) {
-            deleteScriptFunction(name, vimContext)
-            return
-          } else {
-            throw ExException("E130: Unknown function: ${scope.c}:$name")
-          }
-        }
-        else -> throw ExException("E130: Unknown function: ${scope.c}:$name")
-      }
-    }
-
-    if (globalFunctions.containsKey(name)) {
-      globalFunctions[name]!!.isDeleted = true
-      globalFunctions.remove(name)
-      return
-    }
-
-    val firstParentContext = vimContext.getFirstParentContext()
-    if (firstParentContext is Script && getScriptFunction(name, vimContext) != null) {
-      deleteScriptFunction(name, vimContext)
-      return
-    }
-    throw ExException("E130: Unknown function: $name")
-  }
-
-  override fun storeFunction(declaration: FunctionDeclaration) {
-    val scope: Scope = declaration.scope ?: getDefaultFunctionScope()
-    when (scope) {
-      Scope.GLOBAL_VARIABLE -> {
-        if (globalFunctions.containsKey(declaration.name) && !declaration.replaceExisting) {
-          throw ExException("E122: Function ${declaration.name} already exists, add ! to replace it")
-        } else {
-          globalFunctions[declaration.name] = declaration
-        }
-      }
-      Scope.SCRIPT_VARIABLE -> {
-        if (declaration.getFirstParentContext() !is Script) {
-          throw ExException("E81: Using <SID> not in a script context")
-        }
-
-        if (getScriptFunction(declaration.name, declaration) != null && !declaration.replaceExisting) {
-          throw ExException("E122: Function ${declaration.name} already exists, add ! to replace it")
-        } else {
-          storeScriptFunction(declaration)
-        }
-      }
-      else -> throw ExException("E884: Function name cannot contain a colon: ${scope.c}:${declaration.name}")
-    }
-  }
-
-  override fun getFunctionHandler(scope: Scope?, name: String, vimContext: VimLContext): FunctionHandler {
-    return getFunctionHandlerOrNull(scope, name, vimContext)
-      ?: throw ExException("E117: Unknown function: ${scope?.toString() ?: ""}$name")
-  }
-
-  override fun getFunctionHandlerOrNull(scope: Scope?, name: String, vimContext: VimLContext): FunctionHandler? {
-    if (scope == null) {
-      val builtInFunction = getBuiltInFunction(name)
-      if (builtInFunction != null) {
-        return builtInFunction
-      }
-    }
-
-    val definedFunction = getUserDefinedFunction(scope, name, vimContext)
-    if (definedFunction != null) {
-      return DefinedFunctionHandler(definedFunction)
-    }
-    return null
-  }
-
-  override fun getUserDefinedFunction(scope: Scope?, name: String, vimContext: VimLContext): FunctionDeclaration? {
-    return when (scope) {
-      Scope.GLOBAL_VARIABLE -> globalFunctions[name]
-      Scope.SCRIPT_VARIABLE -> getScriptFunction(name, vimContext)
-      null -> {
-        val firstParentContext = vimContext.getFirstParentContext()
-        when (firstParentContext) {
-          is CommandLineVimLContext -> globalFunctions[name]
-          is Script -> globalFunctions[name] ?: getScriptFunction(name, vimContext)
-          else -> throw RuntimeException("Unknown parent context")
-        }
-      }
-      else -> null
-    }
-  }
-
-  override fun getBuiltInFunction(name: String): FunctionHandler? {
-    return builtInFunctions[name]?.instance
-  }
-
-  private fun storeScriptFunction(functionDeclaration: FunctionDeclaration) {
-    val script = functionDeclaration.getScript() ?: throw ExException("E81: Using <SID> not in a script context")
-    script.scriptFunctions[functionDeclaration.name] = functionDeclaration
-  }
-
-  private fun getScriptFunction(name: String, vimContext: VimLContext): FunctionDeclaration? {
-    val script = vimContext.getScript() ?: throw ExException("E120: Using <SID> not in a script context: s:$name")
-    return script.scriptFunctions[name]
-  }
-
-  private fun deleteScriptFunction(name: String, vimContext: VimLContext) {
-    val script = vimContext.getScript() ?: throw ExException("E81: Using <SID> not in a script context")
-    if (script.scriptFunctions[name] != null) {
-      script.scriptFunctions[name]!!.isDeleted = true
-    }
-    script.scriptFunctions.remove(name)
-  }
-
-  private fun getDefaultFunctionScope(): Scope {
-    return Scope.GLOBAL_VARIABLE
-  }
-
-  override fun registerHandlers() {
-    val engineFunctions = EngineFunctionProvider.getFunctions()
-    engineFunctions.forEach { addHandler(it) }
-
-    val intellijFunctions = IntellijFunctionProvider.getFunctions()
-    intellijFunctions.forEach { addHandler(it) }
-  }
-
-  override fun addHandler(handler: LazyVimscriptFunction) {
-    builtInFunctions[handler.name] = handler
-  }
+internal class FunctionStorage : VimScriptFunctionServiceBase() {
+  override val functionProviders: List<VimscriptFunctionProvider> = listOf(EngineFunctionProvider, IntellijFunctionProvider)
 }
diff --git a/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimScriptFunctionServiceBase.kt b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimScriptFunctionServiceBase.kt
new file mode 100644
index 000000000..95add2a4e
--- /dev/null
+++ b/vim-engine/src/main/kotlin/com/maddyhome/idea/vim/api/VimScriptFunctionServiceBase.kt
@@ -0,0 +1,172 @@
+/*
+ * Copyright 2003-2024 The IdeaVim authors
+ *
+ * Use of this source code is governed by an MIT-style
+ * license that can be found in the LICENSE.txt file or at
+ * https://opensource.org/licenses/MIT.
+ */
+
+package com.maddyhome.idea.vim.api
+
+import com.maddyhome.idea.vim.diagnostic.vimLogger
+import com.maddyhome.idea.vim.ex.ExException
+import com.maddyhome.idea.vim.vimscript.model.CommandLineVimLContext
+import com.maddyhome.idea.vim.vimscript.model.Script
+import com.maddyhome.idea.vim.vimscript.model.VimLContext
+import com.maddyhome.idea.vim.vimscript.model.expressions.Scope
+import com.maddyhome.idea.vim.vimscript.model.functions.DefinedFunctionHandler
+import com.maddyhome.idea.vim.vimscript.model.functions.EngineFunctionProvider
+import com.maddyhome.idea.vim.vimscript.model.functions.FunctionHandler
+import com.maddyhome.idea.vim.vimscript.model.functions.LazyVimscriptFunction
+import com.maddyhome.idea.vim.vimscript.model.functions.VimscriptFunctionProvider
+import com.maddyhome.idea.vim.vimscript.model.statements.FunctionDeclaration
+
+public abstract class VimScriptFunctionServiceBase : VimscriptFunctionService {
+  private val logger = vimLogger<VimScriptFunctionServiceBase>()
+
+  protected abstract val functionProviders: List<VimscriptFunctionProvider>
+
+  private val globalFunctions: MutableMap<String, FunctionDeclaration> = mutableMapOf()
+  private val builtInFunctions: MutableMap<String, LazyVimscriptFunction> = mutableMapOf()
+
+  override fun deleteFunction(name: String, scope: Scope?, vimContext: VimLContext) {
+    if (name[0].isLowerCase() && scope != Scope.SCRIPT_VARIABLE) {
+      throw ExException("E128: Function name must start with a capital or \"s:\": $name")
+    }
+
+    if (scope != null) {
+      when (scope) {
+        Scope.GLOBAL_VARIABLE -> {
+          if (globalFunctions.containsKey(name)) {
+            globalFunctions[name]!!.isDeleted = true
+            globalFunctions.remove(name)
+            return
+          } else {
+            throw ExException("E130: Unknown function: ${scope.c}:$name")
+          }
+        }
+        Scope.SCRIPT_VARIABLE -> {
+          if (vimContext.getFirstParentContext() !is Script) {
+            throw ExException("E81: Using <SID> not in a script context")
+          }
+
+          if (getScriptFunction(name, vimContext) != null) {
+            deleteScriptFunction(name, vimContext)
+            return
+          } else {
+            throw ExException("E130: Unknown function: ${scope.c}:$name")
+          }
+        }
+        else -> throw ExException("E130: Unknown function: ${scope.c}:$name")
+      }
+    }
+
+    if (globalFunctions.containsKey(name)) {
+      globalFunctions[name]!!.isDeleted = true
+      globalFunctions.remove(name)
+      return
+    }
+
+    val firstParentContext = vimContext.getFirstParentContext()
+    if (firstParentContext is Script && getScriptFunction(name, vimContext) != null) {
+      deleteScriptFunction(name, vimContext)
+      return
+    }
+    throw ExException("E130: Unknown function: $name")
+  }
+
+  override fun storeFunction(declaration: FunctionDeclaration) {
+    val scope: Scope = declaration.scope ?: getDefaultFunctionScope()
+    when (scope) {
+      Scope.GLOBAL_VARIABLE -> {
+        if (globalFunctions.containsKey(declaration.name) && !declaration.replaceExisting) {
+          throw ExException("E122: Function ${declaration.name} already exists, add ! to replace it")
+        } else {
+          globalFunctions[declaration.name] = declaration
+        }
+      }
+      Scope.SCRIPT_VARIABLE -> {
+        if (declaration.getFirstParentContext() !is Script) {
+          throw ExException("E81: Using <SID> not in a script context")
+        }
+
+        if (getScriptFunction(declaration.name, declaration) != null && !declaration.replaceExisting) {
+          throw ExException("E122: Function ${declaration.name} already exists, add ! to replace it")
+        } else {
+          storeScriptFunction(declaration)
+        }
+      }
+      else -> throw ExException("E884: Function name cannot contain a colon: ${scope.c}:${declaration.name}")
+    }
+  }
+
+  override fun getFunctionHandler(scope: Scope?, name: String, vimContext: VimLContext): FunctionHandler {
+    return getFunctionHandlerOrNull(scope, name, vimContext)
+      ?: throw ExException("E117: Unknown function: ${scope?.toString() ?: ""}$name")
+  }
+
+  override fun getFunctionHandlerOrNull(scope: Scope?, name: String, vimContext: VimLContext): FunctionHandler? {
+    if (scope == null) {
+      val builtInFunction = getBuiltInFunction(name)
+      if (builtInFunction != null) {
+        return builtInFunction
+      }
+    }
+
+    val definedFunction = getUserDefinedFunction(scope, name, vimContext)
+    if (definedFunction != null) {
+      return DefinedFunctionHandler(definedFunction)
+    }
+    return null
+  }
+
+  override fun getUserDefinedFunction(scope: Scope?, name: String, vimContext: VimLContext): FunctionDeclaration? {
+    return when (scope) {
+      Scope.GLOBAL_VARIABLE -> globalFunctions[name]
+      Scope.SCRIPT_VARIABLE -> getScriptFunction(name, vimContext)
+      null -> {
+        val firstParentContext = vimContext.getFirstParentContext()
+        when (firstParentContext) {
+          is CommandLineVimLContext -> globalFunctions[name]
+          is Script -> globalFunctions[name] ?: getScriptFunction(name, vimContext)
+          else -> throw RuntimeException("Unknown parent context")
+        }
+      }
+      else -> null
+    }
+  }
+
+  override fun getBuiltInFunction(name: String): FunctionHandler? {
+    return builtInFunctions[name]?.instance
+  }
+
+  private fun storeScriptFunction(functionDeclaration: FunctionDeclaration) {
+    val script = functionDeclaration.getScript() ?: throw ExException("E81: Using <SID> not in a script context")
+    script.scriptFunctions[functionDeclaration.name] = functionDeclaration
+  }
+
+  private fun getScriptFunction(name: String, vimContext: VimLContext): FunctionDeclaration? {
+    val script = vimContext.getScript() ?: throw ExException("E120: Using <SID> not in a script context: s:$name")
+    return script.scriptFunctions[name]
+  }
+
+  private fun deleteScriptFunction(name: String, vimContext: VimLContext) {
+    val script = vimContext.getScript() ?: throw ExException("E81: Using <SID> not in a script context")
+    if (script.scriptFunctions[name] != null) {
+      script.scriptFunctions[name]!!.isDeleted = true
+    }
+    script.scriptFunctions.remove(name)
+  }
+
+  private fun getDefaultFunctionScope(): Scope {
+    return Scope.GLOBAL_VARIABLE
+  }
+
+  override fun registerHandlers() {
+    functionProviders.forEach { provider -> provider.getFunctions().forEach { addHandler(it) } }
+  }
+
+  override fun addHandler(handler: LazyVimscriptFunction) {
+    builtInFunctions[handler.name] = handler
+  }
+}
\ No newline at end of file