From 444b0f43eb9d54143a265743e9618c32675c5ffb Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Thu, 6 Feb 2025 22:25:16 +0100
Subject: [PATCH 01/46] Update README.md to introduce functorcoder AI coding
assistant and outline project features and structure
---
README.md | 61 +++++++++++++++++++++++++++++++++++++++++++++++--------
1 file changed, 53 insertions(+), 8 deletions(-)
diff --git a/README.md b/README.md
index cecdcb8..e85d685 100644
--- a/README.md
+++ b/README.md
@@ -1,11 +1,56 @@
-# VSCode Extension in Scala.js
-Write vscode extensions in Scala.js! This is a collection of examples and templates to get you started, with convenient sbt tasks to build and run your extension.
-
-contains:
-- commands from the vscode command palette
-- inline completion like github copilot
-- language server protocol client
-- code actions (when pressing Alt+Enter at a code location)
+# functorcoder
+Open source AI coding assistant "**functorcoder**" is a AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
+
+features:
+- code generation: completion, documentation
+- code modification: refactoring, optimization, bug fixing
+- code analysis: code understanding, code review, code quality
+
+we think in mathematics, algebra and functional programming.
+
+ Input = {Query, CodeSnippet, Spec}: The set of all possible input types (queries, code snippets, or requirements/specifications).
+
+ Output = {Code, Explanation, Transformation, DebugSuggestion}: The set of all possible outputs.
+
+The types and objects for Input:
+- code snippet or code file: a piece of code
+- code context: a code snippet with its surrounding code
+- query: natural language query
+- specification: natural language specification
+
+The Output:
+- code snippet or code file: a piece of code, including completion, refactoring, optimization, bug fixing
+- explanation: a natural language explanation
+- transformation: the transformation of the input code
+- suggestion: a suggestion for debugging or improvement or refactoring
+
+
+## Project Structure
+
+```bash
+/ai-coding-assistant
+├── /src
+│ ├── /core
+│ │ ├── AIEngine.scala # Core engine with main orchestration logic
+│ │ └── Utils.scala # Helper utilities
+│ ├── /actions
+│ │ ├── CodeCompletion.scala # Code completion module
+│ │ ├── Refactor.scala # Refactor code module
+│ │ └── Debug.scala # Debugging module
+│ ├── /types
+│ │ ├── InputTypes.scala # Types for code, context, and user actions
+│ │ └── OutputTypes.scala # Types for output (formatted code, suggestions)
+│ ├── /editorUI
+│ │ ├── EditorIntegration.scala# Integration with the editor (e.g., VSCode)
+│ └── /tests
+│ ├── CoreTests.scala # Unit tests for core modules
+│ ├── ActionTests.scala # Unit tests for actions like code completion
+│ └── EditorTests.scala # Tests for editor integration
+└── /docs
+ ├── README.md # Project overview and setup instructions
+ ├── ARCHITECTURE.md # Architecture details and design decisions
+ └── API.md # API documentation for integration
+```
### Setup
Requirements:
From e48e8517e582588155aa6cf36cb6786bb31f0528 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 7 Feb 2025 11:42:45 +0100
Subject: [PATCH 02/46] Update README.md to include project structure and bash
commands for setup
---
README.md | 23 +++++++++++++++++++++--
1 file changed, 21 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index e85d685..077c07d 100644
--- a/README.md
+++ b/README.md
@@ -26,10 +26,12 @@ The Output:
## Project Structure
+package name: com.functorcoder
+project file structure:
```bash
-/ai-coding-assistant
-├── /src
+/functorcoder
+├── /src/main/scala/functorcoder
│ ├── /core
│ │ ├── AIEngine.scala # Core engine with main orchestration logic
│ │ └── Utils.scala # Helper utilities
@@ -52,6 +54,23 @@ The Output:
└── API.md # API documentation for integration
```
+The bash commands to create above structure(folders and files) are:
+```bash
+mkdir -p src/main/scala/functorcoder/core
+mkdir -p src/main/scala/functorcoder/actions
+mkdir -p src/main/scala/functorcoder/types
+mkdir -p src/main/scala/functorcoder/editorUI
+mkdir -p src/main/scala/functorcoder/tests
+mkdir -p docs
+touch src/main/scala/functorcoder/core/AIEngine.scala
+touch src/main/scala/functorcoder/core/Utils.scala
+touch src/main/scala/functorcoder/actions/CodeCompletion.scala
+touch src/main/scala/functorcoder/actions/Refactor.scala
+touch src/main/scala/functorcoder/actions/Debug.scala
+
+touch src/main/scala/functorcoder/types/InputTypes.scala
+```
+
### Setup
Requirements:
- [Sbt](https://www.scala-sbt.org/download.html)
From 39a08efa7cb8535f0ed0ef37562ed7817371a808 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 10 Feb 2025 16:18:01 +0100
Subject: [PATCH 03/46] Add initial implementation of core components and
configuration for functorcoder
---
README.md | 5 +
build.sbt | 8 +-
package.json | 14 ++
.../functorcoder/actions/CodeCompletion.scala | 1 +
.../scala/functorcoder/actions/Debug.scala | 1 +
.../scala/functorcoder/actions/Refactor.scala | 0
.../functorcoder/editorUI/editorConfig.scala | 39 +++++
src/main/scala/functorcoder/llm/llmMain.scala | 111 +++++++++++++
.../scala/functorcoder/llm/llmPrompt.scala | 99 ++++++++++++
.../functorcoder/llm/openAIResponse.scala | 84 ++++++++++
.../scala/functorcoder/llm/openaiReq.scala | 151 ++++++++++++++++++
.../scala/vscextension/extensionMain.scala | 18 +--
.../vscextension/inlineCompletions.scala | 45 +++---
13 files changed, 542 insertions(+), 34 deletions(-)
create mode 100644 src/main/scala/functorcoder/actions/CodeCompletion.scala
create mode 100644 src/main/scala/functorcoder/actions/Debug.scala
create mode 100644 src/main/scala/functorcoder/actions/Refactor.scala
create mode 100644 src/main/scala/functorcoder/editorUI/editorConfig.scala
create mode 100644 src/main/scala/functorcoder/llm/llmMain.scala
create mode 100644 src/main/scala/functorcoder/llm/llmPrompt.scala
create mode 100644 src/main/scala/functorcoder/llm/openAIResponse.scala
create mode 100644 src/main/scala/functorcoder/llm/openaiReq.scala
diff --git a/README.md b/README.md
index 077c07d..f9f8574 100644
--- a/README.md
+++ b/README.md
@@ -35,6 +35,8 @@ project file structure:
│ ├── /core
│ │ ├── AIEngine.scala # Core engine with main orchestration logic
│ │ └── Utils.scala # Helper utilities
+│ ├── /llm
+│ │ ├── LLM.scala # Large Language Model (LLM) integration
│ ├── /actions
│ │ ├── CodeCompletion.scala # Code completion module
│ │ ├── Refactor.scala # Refactor code module
@@ -57,13 +59,16 @@ project file structure:
The bash commands to create above structure(folders and files) are:
```bash
mkdir -p src/main/scala/functorcoder/core
+mkdir -p src/main/scala/functorcoder/llm
mkdir -p src/main/scala/functorcoder/actions
mkdir -p src/main/scala/functorcoder/types
mkdir -p src/main/scala/functorcoder/editorUI
mkdir -p src/main/scala/functorcoder/tests
mkdir -p docs
+
touch src/main/scala/functorcoder/core/AIEngine.scala
touch src/main/scala/functorcoder/core/Utils.scala
+touch src/main/scala/functorcoder/llm/LLM.scala
touch src/main/scala/functorcoder/actions/CodeCompletion.scala
touch src/main/scala/functorcoder/actions/Refactor.scala
touch src/main/scala/functorcoder/actions/Debug.scala
diff --git a/build.sbt b/build.sbt
index 1d30292..1c647aa 100644
--- a/build.sbt
+++ b/build.sbt
@@ -12,9 +12,9 @@ lazy val root = project
ScalaJSBundlerPlugin,
ScalablyTypedConverterPlugin
)
- .configs(IntegrationTest)
- .settings(Defaults.itSettings: _*)
- .settings(inConfig(IntegrationTest)(ScalaJSPlugin.testConfigSettings): _*)
+ // .configs(IntegrationTest)
+ // .settings(Defaults.itSettings: _*)
+ // .settings(inConfig(IntegrationTest)(ScalaJSPlugin.testConfigSettings): _*)
.settings(
moduleName := "vscextension",
organization := "com.doofin",
@@ -29,6 +29,8 @@ lazy val root = project
Compile / fullOptJS / artifactPath := baseDirectory.value / "out" / "extension.js",
libraryDependencies ++= Seq(
// "com.lihaoyi" %%% "utest" % "0.8.2" % "test",
+ // ("org.latestbit", "circe-tagged-adt-codec", "0.11.0")
+ "org.latestbit" %%% "circe-tagged-adt-codec" % "0.11.0",
"org.scalameta" %%% "munit" % "0.7.29" % Test
),
Compile / npmDependencies ++=
diff --git a/package.json b/package.json
index 8d11506..8f2037e 100644
--- a/package.json
+++ b/package.json
@@ -22,6 +22,20 @@
"@types/vscode": "^1.73.0"
},
"contributes": {
+ "configuration": {
+ "type": "object",
+ "title": "functorcoder",
+ "properties": {
+ "openaiApiKey": {
+ "type": "string",
+ "default": ""
+ },
+ "openaiUrl": {
+ "type": "string",
+ "default": ""
+ }
+ }
+ },
"commands": [
{
"command": "extension.helloWorld",
diff --git a/src/main/scala/functorcoder/actions/CodeCompletion.scala b/src/main/scala/functorcoder/actions/CodeCompletion.scala
new file mode 100644
index 0000000..fe4de50
--- /dev/null
+++ b/src/main/scala/functorcoder/actions/CodeCompletion.scala
@@ -0,0 +1 @@
+package functorcoder.actions
diff --git a/src/main/scala/functorcoder/actions/Debug.scala b/src/main/scala/functorcoder/actions/Debug.scala
new file mode 100644
index 0000000..fe4de50
--- /dev/null
+++ b/src/main/scala/functorcoder/actions/Debug.scala
@@ -0,0 +1 @@
+package functorcoder.actions
diff --git a/src/main/scala/functorcoder/actions/Refactor.scala b/src/main/scala/functorcoder/actions/Refactor.scala
new file mode 100644
index 0000000..e69de29
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
new file mode 100644
index 0000000..309da8f
--- /dev/null
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -0,0 +1,39 @@
+package functorcoder.editorUI
+
+import typings.vscode.mod as vscode
+// https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
+object editorConfig {
+ case class Config(openaiApiKey: String, openaiUrl: String)
+
+ /** read the configuration from the settings.json file
+ *
+ * @return
+ * the configuration object
+ */
+ def readConfig() = {
+ val config = vscode.workspace.getConfiguration("functorcoder")
+ val openaiApiKey =
+ config.getStringOrEmpty("openaiApiKey")
+
+ val openaiUrl =
+ config.getStringOrEmpty("openaiUrl", "https://api.openai.com/v1/chat/completions")
+
+ Config(openaiApiKey, openaiUrl)
+ }
+
+ extension (config: vscode.WorkspaceConfiguration) {
+
+ /** get a string from the configuration or return an empty string if default is not provided
+ *
+ * @param str
+ * the string to get
+ * @param default
+ * the default value
+ * @return
+ * the string or an empty string
+ */
+ def getStringOrEmpty(str: String, default: String = "") = {
+ config.get[String](str).toOption.getOrElse(default)
+ }
+ }
+}
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
new file mode 100644
index 0000000..3fb687b
--- /dev/null
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -0,0 +1,111 @@
+package functorcoder.llm
+
+import openaiReq.*
+import vscextension.io.network.*
+import typings.nodeFetch.mod as nodeFetch
+
+import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
+import scala.scalajs.js
+import scala.util.{Failure, Success}
+
+import vscextension.facade.NodeFetch.*
+import vscextension.facade.vscodeUtils.*
+
+import scala.scalajs.js.Thenable.Implicits.*
+import scala.concurrent.Future
+
+import functorcoder.editorUI.editorConfig
+
+/** large language model (LLM) AI main
+ *
+ * use node-fetch for network requests
+ */
+object llmMain {
+
+ /** generate a completion prompt
+ *
+ * change the model here if needed
+ *
+ * @param completionPrompt
+ * completion prompt object
+ * @return
+ */
+ def getCompletionPrompt(completionPrompt: llmPrompt.Completion) = {
+ openaiReq
+ .OpenAiRequest(
+ List(
+ openaiReq.Message(roles.user, completionPrompt.codeWithHole),
+ openaiReq.Message(roles.system, completionPrompt.assistantMessage)
+ ),
+ openaiReq.models.gpt4o
+ )
+ .toJson
+ }
+
+ case class llmAgent(editorCfg: editorConfig.Config) {
+
+ val url = editorCfg.openaiUrl
+ val apiKey = editorCfg.openaiApiKey
+
+ /** get code completion from openai asynchrnously
+ *
+ * @param holeToCode
+ * a function that takes a hole name and returns the code to fill the hole
+ *
+ * so we will call this function with the hole "{{FILL_HERE}}" you insert it in the code
+ */
+ def getCodeCompletion(holeToCode: String => String) = {
+ val requestStr = getCompletionPrompt(
+ llmPrompt
+ .Completion(holeToCode("{{FILL_HERE}}"))
+ )
+
+ val requestOptions = getRequestOptions(requestStr)
+
+ val responseFuture =
+ nodeFetch.default(url, requestOptions)
+
+ getResponseText(responseFuture)
+ }
+
+ private def getRequestOptions(requestStr: String) = {
+ new nodeFetch.RequestInit {
+ method = "POST"
+ headers = new nodeFetch.Headers {
+ append("Content-Type", "application/json")
+ append("Authorization", s"Bearer $apiKey")
+ }
+ body = requestStr
+ }
+ }
+
+ private def getResponseText(responseFuture: Future[nodeFetch.Response]) = {
+ for {
+ res <- responseFuture
+ body <- res
+ .json()
+ .toFuture
+ .asInstanceOf[Future[js.Object]]
+ .map(x => js.JSON.stringify(x))
+ } yield {
+ // the body of the response
+ // showMessageAndLog(s"openai response: $body")
+ val decodedResponse =
+ openAIResponse.decodeOpenAIResponse(body)
+ decodedResponse match {
+ case Left(err) =>
+ // return an empty string if failed
+ showMessageAndLog(s"error parsing openai response: $err")
+ ""
+ case Right(resp) =>
+ // return the first choice
+ resp.choices.headOption match {
+ case Some(choice) => choice.message.content
+ case None => ""
+ }
+ }
+ }
+ }
+ }
+
+}
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
new file mode 100644
index 0000000..f19fb06
--- /dev/null
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -0,0 +1,99 @@
+package functorcoder.llm
+
+/** large language model (LLM) AI prompt
+ *
+ * for completion, code generation, etc.
+ */
+object llmPrompt {
+
+ /** a configuration for code completion
+ *
+ * https://github.com/continuedev/continue/blob/main/core/autocomplete/templating/AutocompleteTemplate.ts
+ *
+ * @param codeWithHole
+ * code with a hole to fill like {{FILL_HERE}}
+ * @param taskRequirement
+ * like "Fill the {{FILL_HERE}} hole."
+ * @param assistantMessage
+ * like "always give scala code examples." or
+ *
+ * You are a HOLE FILLER. You are provided with a file containing holes, formatted as '{{HOLE_NAME}}'. Your TASK is
+ * to complete with a string to replace this hole with, inside a XML tag, including context-aware
+ * indentation, if needed
+ *
+ * You will complete code strings with a hole {{FILL_HERE}}, you only return the code for the hole.
+ */
+ case class Completion(
+ codeWithHole: String, // code with a hole to fill like {{FILL_HERE}}
+ // taskRequirement: String, // like "Fill the {{FILL_HERE}} hole."
+ assistantMessage: String = prompts.prompt1
+ ) {
+ def generatePrompt = {
+ // shall return a string wrapped with
+ // s"""
+ // |${codeWithHole}
+ // |
+ // |""".stripMargin
+
+ /* |
+ |TASK: ${taskRequirement}
+ */
+ codeWithHole
+ }
+ }
+
+ /** prompts engineering
+ *
+ * more like art than science. just try different prompts and see what works best
+ */
+ object prompts {
+ val prompt1 =
+ "You are a code or text autocompletion assistant. " +
+ "In the provided input, missing code or text are marked as '{{FILL_HERE}}'. " +
+ "Your task is to output only the snippet that replace the placeholder, " +
+ "ensuring that indentation and formatting remain consistent with the context. Don't quote your output"
+ val prompt2 =
+ "You are a hole filler," +
+ "You are given a string with a hole: " +
+ "{{FILL_HERE}} in the string, " +
+ "your task is to replace this hole with your reply." +
+ "you only return the string for the hole with indentation, without any quotes"
+ }
+}
+
+/* example:
+
+function sum_evens(lim) {
+ var sum = 0;
+ for (var i = 0; i < lim; ++i) {
+ {{FILL_HERE}}
+ }
+ return sum;
+}
+
+
+TASK: Fill the {{FILL_HERE}} hole.
+
+## CORRECT COMPLETION
+
+if (i % 2 === 0) {
+ sum += i;
+ }
+
+## EXAMPLE QUERY:
+
+
+def sum_list(lst):
+ total = 0
+ for x in lst:
+ {{FILL_HERE}}
+ return total
+
+print sum_list([1, 2, 3])
+
+
+## CORRECT COMPLETION:
+
+ total += x
+
+ */
diff --git a/src/main/scala/functorcoder/llm/openAIResponse.scala b/src/main/scala/functorcoder/llm/openAIResponse.scala
new file mode 100644
index 0000000..bdf7f58
--- /dev/null
+++ b/src/main/scala/functorcoder/llm/openAIResponse.scala
@@ -0,0 +1,84 @@
+package functorcoder.llm
+
+import io.circe._
+import io.circe.generic.semiauto._
+import io.circe.parser.*
+
+/** response from openAI API
+ *
+ * https://platform.openai.com/docs/api-reference/making-requests
+ */
+object openAIResponse {
+
+ def decodeOpenAIResponse(json: String): Either[Error, OpenAiResponse] = {
+ val res =
+ decode[OpenAiResponse](escapeJsonString(json))
+
+ res match {
+ case x @ Left(value) => x
+ case x @ Right(value) => x
+ }
+ }
+
+ def escapeJsonString(str: String): String = {
+ // replace the escape characters with ,etc.
+ str
+ .replace("\n", "")
+ .replace("\t", "")
+ .replace("\r", "")
+ }
+
+ def reverseEscapeJsonString(str: String): String = {
+ str
+ .replace("", "\n")
+ .replace("", "\t")
+ .replace("", "\r")
+ }
+
+ case class OpenAiResponse(
+ id: String,
+ `object`: String,
+ created: Long,
+ model: String,
+ usage: Usage,
+ choices: List[Choice]
+ )
+
+ case class Choice(
+ message: Message,
+ logprobs: Option[String],
+ finish_reason: String,
+ index: Int
+ )
+
+ case class Message(
+ role: String, // the role
+ content: String // the content from llm
+ )
+
+ case class CompletionTokensDetails(
+ reasoning_tokens: Int,
+ accepted_prediction_tokens: Int,
+ rejected_prediction_tokens: Int
+ )
+
+ case class Usage(
+ prompt_tokens: Int,
+ completion_tokens: Int,
+ total_tokens: Int,
+ completion_tokens_details: CompletionTokensDetails
+ )
+
+// encode and decode
+
+ object Choice {
+ implicit val encoder: Encoder[Choice] = deriveEncoder[Choice]
+ implicit val decoder: Decoder[Choice] = deriveDecoder[Choice]
+ }
+
+ object OpenAiResponse {
+ implicit val encoder: Encoder[OpenAiResponse] = deriveEncoder[OpenAiResponse]
+ implicit val decoder: Decoder[OpenAiResponse] = deriveDecoder[OpenAiResponse]
+ }
+
+}
diff --git a/src/main/scala/functorcoder/llm/openaiReq.scala b/src/main/scala/functorcoder/llm/openaiReq.scala
new file mode 100644
index 0000000..9a2ea85
--- /dev/null
+++ b/src/main/scala/functorcoder/llm/openaiReq.scala
@@ -0,0 +1,151 @@
+package functorcoder.llm
+
+import io.circe.generic.auto._
+
+import io.circe.*
+import io.circe.syntax.*
+
+object openaiReq {
+
+ /** https://platform.openai.com/docs/models/model-endpoint-compatibility
+ *
+ * All GPT-4o, GPT-4o-mini, GPT-4, and GPT-3.5 Turbo models and their dated releases. chatgpt-4o-latest dynamic
+ * model. Fine-tuned versions of gpt-4o, gpt-4o-mini, gpt-4, and gpt-3.5-turbo.
+ */
+ object models {
+ val gpt4o = "gpt-4o" // price is 2.5 per 1M tokens
+ /** gpt-4o-mini is a smaller version of the GPT-4o
+ */
+ val gpt4oMini = "gpt-4o-mini" // price is 0.15 per 1M tokens
+ val gpt4 = "gpt-4"
+ val gpt35Turbo = "gpt-3.5-turbo"
+ val o3mini = "o3-mini" // reasoning model,1.1 per 1M tokens
+ }
+
+ object roles {
+ val user = "user" // user input in request
+ val system = "system" // control messages in request
+ val assistant = "assistant" // the response from the model
+ }
+
+ /** Represents a message in a conversation.
+ *
+ * @param role
+ * The role of the speaker. Must be either "user" or "system".
+ * @param content
+ * The content of the message. This field is required.
+ */
+ case class Message(role: String, content: String)
+
+ /** Represents the request body for interacting with the API.
+ *
+ * @param messages
+ * A list of messages comprising the conversation so far. This field is required.
+ * @param model
+ * The ID of the model to use. This field is required. Refer to the model endpoint compatibility table for details
+ * on which models work with the Chat API.
+ *
+ * below are optional fields
+ *
+ * @param frequency_penalty
+ * Optional. A number between -2.0 and 2.0. Positive values penalize new tokens based on their existing frequency
+ * in the text so far, decreasing the model's likelihood to repeat the same line verbatim. Defaults to None.
+ * @param logit_bias
+ * Optional. A map that modifies the likelihood of specified tokens appearing in the completion. Accepts a JSON
+ * object that maps tokens (specified by their token ID in the tokenizer) to an associated bias value from -100 to
+ * 100. The bias is added to the logits generated by the model prior to sampling. The exact effect varies per
+ * model. Defaults to None.
+ * @param logprobs
+ * Optional. Whether to return log probabilities of the output tokens. If true, returns the log probabilities of
+ * each output token returned in the content of the message. Defaults to None.
+ * @param top_logprobs
+ * Optional. An integer between 0 and 20 specifying the number of most likely tokens to return at each token
+ * position, each with an associated log probability. Must be set if `logprobs` is true. Defaults to None.
+ * @param max_tokens
+ * Optional. The maximum number of tokens that can be generated in the chat completion. The total length of input
+ * tokens and generated tokens is limited by the model's context length. Defaults to None.
+ * @param n
+ * Optional. How many chat completion choices to generate for each input message. Note that you will be charged
+ * based on the number of generated tokens across all choices. Defaults to None.
+ * @param presence_penalty
+ * Optional. A number between -2.0 and 2.0. Positive values penalize new tokens based on whether they appear in the
+ * text so far, increasing the model's likelihood to talk about new topics. Defaults to None.
+ * @param response_format
+ * Optional. An object specifying the format that the model must output. Compatible with GPT-4o, GPT-4o mini, GPT-4
+ * Turbo, and all GPT-3.5 Turbo models newer than gpt-3.5-turbo-1106. Defaults to None.
+ * @param seed
+ * Optional. If specified, the system will make a best effort to sample deterministically. Repeated requests with
+ * the same seed and parameters should return the same result. Determinism is not guaranteed. Defaults to None.
+ * @param service_tier
+ * Optional. Specifies the latency tier to use for processing the request. If set to 'auto', the system will
+ * utilize scale tier credits until they are exhausted. If set to 'default', the request will be processed using
+ * the default service tier with a lower uptime SLA and no latency guarantee. When not set, the default behavior is
+ * 'auto'. Defaults to None.
+ * @param stop
+ * Optional. Up to 4 sequences where the API will stop generating further tokens. Defaults to None.
+ * @param stream
+ * Optional. If set, partial message deltas will be sent as data-only server-sent events as they become available,
+ * with the stream terminated by a `data: [DONE]` message. Defaults to None.
+ * @param stream_options
+ * Optional. Options for streaming response. Only set this when you set `stream` to true. Defaults to None.
+ * @param temperature
+ * Optional. The sampling temperature to use, between 0 and 2. Higher values like 0.8 will make the output more
+ * random, while lower values like 0.2 will make it more focused and deterministic. Defaults to None.
+ * @param top_p
+ * Optional. An alternative to sampling with temperature, called nucleus sampling, where the model considers the
+ * results of the tokens with top_p probability mass. Defaults to None.
+ * @param tools
+ * Optional. A list of tools the model may call. Currently, only functions are supported as a tool. A max of 128
+ * functions are supported. Defaults to None.
+ * @param tool_choice
+ * Optional. Controls which (if any) tool is called by the model. Defaults to `none` when no tools are present, and
+ * `auto` if tools are present. Defaults to None.
+ * @param parallel_tool_calls
+ * Optional. Whether to enable parallel function calling during tool use. Defaults to None.
+ * @param user
+ * Optional. A unique identifier representing your end-user, which can help monitor and detect abuse. Defaults to
+ * None.
+ */
+ case class OpenAiRequest(
+ messages: Seq[Message],
+ model: String
+ // frequency_penalty: Option[Double] = None,
+ // logit_bias: Option[Map[String, Int]] = None,
+ // logprobs: Option[Boolean] = None,
+ // top_logprobs: Option[Int] = None,
+ // max_tokens: Option[Int] = None,
+ // n: Option[Int] = None,
+ // presence_penalty: Option[Double] = None,
+ // response_format: Option[String] = None,
+ // seed: Option[Int] = None,
+ // service_tier: Option[String] = None,
+ // stop: Option[Either[String, Seq[String]]] = None,
+ // stream: Option[Boolean] = None,
+ // stream_options: Option[String] = None,
+ // temperature: Option[Double] = None,
+ // top_p: Option[Double] = None,
+ // tools: Option[String] = None,
+ // tool_choice: Option[String] = None,
+ // parallel_tool_calls: Option[Boolean] = None,
+ // user: Option[String] = None
+ ) {
+ def toJson: String = this.asJson.noSpaces
+ }
+
+}
+/* openAI API request
+https://platform.openai.com/docs/api-reference/chat
+'{
+ "model": "gpt-4o",
+ "messages": [
+ {
+ "role": "system",
+ "content": "You are a helpful assistant."
+ },
+ {
+ "role": "user",
+ "content": "Hello!"
+ }
+ ]
+ }'
+ */
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index f711bfc..68eb95a 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -6,6 +6,7 @@ import scala.scalajs.js.annotation.JSExportTopLevel
import typings.vscode.mod as vscode
import facade.vscodeUtils.*
+import functorcoder.editorUI.editorConfig
object extensionMain {
@@ -16,7 +17,8 @@ object extensionMain {
showMessageAndLog("congrats, your scala.js vscode extension is loaded")
val projectRoot = vscode.workspace.rootPath.getOrElse("")
-
+ val cfg = editorConfig.readConfig()
+ showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
commands.registerAllCommands(context)
@@ -32,16 +34,12 @@ object extensionMain {
// code actions like quick fixes
CodeActions.registerCodeActions(context)
- // network requests
- val url = "https://github.com/"
- io.network.httpGet(url)
- io.network.httpGetTyped(url)
-
+ // functorcoder.llm.llmAIMain.test
// file operations
- io.fileIO.createFile(projectRoot)
+ // io.fileIO.createFile(projectRoot)
// load configuration
- val cfg = io.config.loadConfig(projectRoot + "/.vscode/settings.json")
- showMessageAndLog(s"config loaded: $cfg")
+ // val cfg = io.config.loadConfig(projectRoot + "/.vscode/settings.json")
+ // showMessageAndLog(s"config loaded: $cfg")
// language server client
// lsp.startLsp()
@@ -49,6 +47,8 @@ object extensionMain {
// webview
// webview.showWebviewPanel()
+ // editor config
+
}
}
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index 88b76d7..f759985 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -1,10 +1,14 @@
package vscextension
+
import typings.vscode.mod as vscode
+import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import facade.vscodeUtils.*
+import scala.scalajs.js.Promise
+import functorcoder.editorUI.editorConfig
/** demonstrates how to provide inline completions in the editor. like the github copilot
* https://github.com/microsoft/vscode-extension-samples/tree/main/inline-completions
@@ -13,6 +17,9 @@ import facade.vscodeUtils.*
object inlineCompletions {
def createCompletionProvider(): vscode.InlineCompletionItemProvider = {
+ val cfg = editorConfig.readConfig()
+ val llm = functorcoder.llm.llmMain.llmAgent(cfg)
+
new vscode.InlineCompletionItemProvider {
override def provideInlineCompletionItems(
document: vscode.TextDocument, // the current document
@@ -21,29 +28,23 @@ object inlineCompletions {
token: vscode.CancellationToken // to cancel the completion
) = {
- val offset = 0
- // get the line before the current line
- val lineBefore =
- if !(position.line - offset < 0)
- then document.lineAt(position.line - offset).text
- else ""
-
- // the whole line before the cursor
- showMessage(s"line before cursor: $lineBefore")
-
- // always return a list of items, but only first item will be displayed
- val items = List("foo", "bar", "baz")
- // .filter(_.startsWith(word)) // often need to do some filtering
- .map { str =>
- new vscode.InlineCompletionItem(
- insertText = str, // text to insert
- range = new vscode.Range(position, position)
+ val codeBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position))
+ val codeAfter = document.getText(new vscode.Range(position, document.positionAt(document.getText().length)))
+ val r = llm.getCodeCompletion { hole =>
+ val prompt = s"$codeBefore$hole$codeAfter"
+ // showMessageAndLog(s"prompt: $prompt")
+ prompt
+ }
+
+ val providerResultF: Promise[scala.scalajs.js.Array[vscode.InlineCompletionItem]] =
+ r.map(completionText =>
+ js.Array(
+ new vscode.InlineCompletionItem(
+ insertText = completionText, // text to insert
+ range = new vscode.Range(position, position)
+ )
)
- }
-
- // return a promise of the items, useful for async but not needed here
- val providerResultF =
- jsUtils.newJsPromise(items.toJSArray)
+ ).toJSPromise
providerResultF.asInstanceOf[typings.vscode.mod.ProviderResult[
scala.scalajs.js.Array[typings.vscode.mod.InlineCompletionItem] | typings.vscode.mod.InlineCompletionList
From 7c5aec71cce0a494a82e6775da214f9d97d88cb7 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 10 Feb 2025 16:19:17 +0100
Subject: [PATCH 04/46] Refactor code to improve clarity by updating comments
and removing unused imports
---
src/main/scala/functorcoder/editorUI/editorConfig.scala | 2 +-
src/main/scala/functorcoder/llm/llmMain.scala | 3 ---
src/main/scala/vscextension/extensionMain.scala | 2 +-
src/main/scala/vscextension/inlineCompletions.scala | 1 -
4 files changed, 2 insertions(+), 6 deletions(-)
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index 309da8f..5cc9b73 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -5,7 +5,7 @@ import typings.vscode.mod as vscode
object editorConfig {
case class Config(openaiApiKey: String, openaiUrl: String)
- /** read the configuration from the settings.json file
+ /** read the configuration from the vscode settings.json
*
* @return
* the configuration object
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index 3fb687b..21e8c98 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -1,14 +1,11 @@
package functorcoder.llm
import openaiReq.*
-import vscextension.io.network.*
import typings.nodeFetch.mod as nodeFetch
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.scalajs.js
-import scala.util.{Failure, Success}
-import vscextension.facade.NodeFetch.*
import vscextension.facade.vscodeUtils.*
import scala.scalajs.js.Thenable.Implicits.*
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 68eb95a..9679746 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -16,7 +16,7 @@ object extensionMain {
def activate(context: vscode.ExtensionContext): Unit = {
showMessageAndLog("congrats, your scala.js vscode extension is loaded")
- val projectRoot = vscode.workspace.rootPath.getOrElse("")
+ vscode.workspace.rootPath.getOrElse("")
val cfg = editorConfig.readConfig()
showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index f759985..81ef5bb 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -6,7 +6,6 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
-import facade.vscodeUtils.*
import scala.scalajs.js.Promise
import functorcoder.editorUI.editorConfig
From 50e336d793e1a89ad65972e96d3da7e0d7ee84ec Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 10 Feb 2025 16:44:33 +0100
Subject: [PATCH 05/46] Update README.md to clarify project structure and
features; modify package.json for proper naming and description; enhance
editorConfig.scala for improved parameter clarity
---
README.md | 36 +++++++++----------
package.json | 8 ++---
.../functorcoder/editorUI/editorConfig.scala | 6 ++--
3 files changed, 24 insertions(+), 26 deletions(-)
diff --git a/README.md b/README.md
index f9f8574..808fb4b 100644
--- a/README.md
+++ b/README.md
@@ -27,14 +27,15 @@ The Output:
## Project Structure
package name: com.functorcoder
+It is the core module of the AI coding assistant, including below features:
+- Large Language Model (LLM) integration
+- sending propmt to LLM and getting the response
+
project file structure:
```bash
/functorcoder
├── /src/main/scala/functorcoder
-│ ├── /core
-│ │ ├── AIEngine.scala # Core engine with main orchestration logic
-│ │ └── Utils.scala # Helper utilities
│ ├── /llm
│ │ ├── LLM.scala # Large Language Model (LLM) integration
│ ├── /actions
@@ -45,33 +46,30 @@ project file structure:
│ │ ├── InputTypes.scala # Types for code, context, and user actions
│ │ └── OutputTypes.scala # Types for output (formatted code, suggestions)
│ ├── /editorUI
-│ │ ├── EditorIntegration.scala# Integration with the editor (e.g., VSCode)
+│ │ ├── EditorIntegration.scala # Integration with the editor (e.g., VSCode)
│ └── /tests
│ ├── CoreTests.scala # Unit tests for core modules
-│ ├── ActionTests.scala # Unit tests for actions like code completion
-│ └── EditorTests.scala # Tests for editor integration
└── /docs
├── README.md # Project overview and setup instructions
├── ARCHITECTURE.md # Architecture details and design decisions
└── API.md # API documentation for integration
```
+The vscode extension package will be in the `vscextension` folder, which is in charge of integrating the AI part with the VSCode editor. It's adopted from the [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) project.
+
+```bash
+/vscextension
+├── /src/main/scala/vscextension
+│ ├── extensionMain.scala # Main entry point for the extension
+│ ├── commands.scala # Command definitions
+│ ├── codeActions.scala # Code action definitions
+...
+```
+
+
The bash commands to create above structure(folders and files) are:
```bash
-mkdir -p src/main/scala/functorcoder/core
-mkdir -p src/main/scala/functorcoder/llm
-mkdir -p src/main/scala/functorcoder/actions
mkdir -p src/main/scala/functorcoder/types
-mkdir -p src/main/scala/functorcoder/editorUI
-mkdir -p src/main/scala/functorcoder/tests
-mkdir -p docs
-
-touch src/main/scala/functorcoder/core/AIEngine.scala
-touch src/main/scala/functorcoder/core/Utils.scala
-touch src/main/scala/functorcoder/llm/LLM.scala
-touch src/main/scala/functorcoder/actions/CodeCompletion.scala
-touch src/main/scala/functorcoder/actions/Refactor.scala
-touch src/main/scala/functorcoder/actions/Debug.scala
touch src/main/scala/functorcoder/types/InputTypes.scala
```
diff --git a/package.json b/package.json
index 8f2037e..f475601 100644
--- a/package.json
+++ b/package.json
@@ -1,9 +1,9 @@
{
- "name": "vscode-scalajs-hello",
- "displayName": "vscode-scalajs-hello",
- "description": "",
+ "name": "functorcoder",
+ "displayName": "functorcoder",
+ "description": "an ai coding assistant",
"version": "0.0.1",
- "publisher": "test",
+ "publisher": "functorcoder.com",
"categories": [
"Other"
],
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index 5cc9b73..26820ed 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -16,7 +16,7 @@ object editorConfig {
config.getStringOrEmpty("openaiApiKey")
val openaiUrl =
- config.getStringOrEmpty("openaiUrl", "https://api.openai.com/v1/chat/completions")
+ config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
Config(openaiApiKey, openaiUrl)
}
@@ -32,8 +32,8 @@ object editorConfig {
* @return
* the string or an empty string
*/
- def getStringOrEmpty(str: String, default: String = "") = {
- config.get[String](str).toOption.getOrElse(default)
+ def getStringOrEmpty(key: String, default: String = "") = {
+ config.get[String](key).toOption.getOrElse(default)
}
}
}
From ae074a768da73095f9b78596df1398ebdb10f9ac Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 16:32:02 +0100
Subject: [PATCH 06/46] Implement command registration and status bar
integration for functorcoder; add command actions and quick pick
functionality
---
.../scala/functorcoder/actions/Commands.scala | 26 ++++++++++++++++++
.../scala/functorcoder/types/editorCtx.scala | 13 +++++++++
src/main/scala/vscextension/commands.scala | 27 +++++++------------
.../scala/vscextension/extensionMain.scala | 4 +++
src/main/scala/vscextension/quickPick.scala | 21 +++++++--------
src/main/scala/vscextension/statusBar.scala | 22 +++++++++++++++
6 files changed, 85 insertions(+), 28 deletions(-)
create mode 100644 src/main/scala/functorcoder/actions/Commands.scala
create mode 100644 src/main/scala/functorcoder/types/editorCtx.scala
create mode 100644 src/main/scala/vscextension/statusBar.scala
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
new file mode 100644
index 0000000..f6a2c83
--- /dev/null
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -0,0 +1,26 @@
+package functorcoder.actions
+
+import typings.vscode.mod as vscode
+
+import scala.collection.immutable
+import scala.scalajs.js
+
+import vscextension.quickPick
+
+/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
+ */
+object Commands {
+ type CommandT = Any => Unit
+ // all the commands here
+ val commandMenu = "functorcoder.menu"
+
+ val commandList: Seq[(String, CommandT)] =
+ Seq(
+ (commandMenu, quickPick.showQuickPick)
+ )
+
+ // the main menu items
+ val mainMenuItems: Seq[(String, () => Unit)] = Seq(
+ "create files" -> { () => println("create files") }
+ )
+}
diff --git a/src/main/scala/functorcoder/types/editorCtx.scala b/src/main/scala/functorcoder/types/editorCtx.scala
new file mode 100644
index 0000000..575d2e4
--- /dev/null
+++ b/src/main/scala/functorcoder/types/editorCtx.scala
@@ -0,0 +1,13 @@
+package functorcoder.types
+
+object editorCtx {
+
+ /** the context of the editor
+ *
+ * @param language
+ * the programming language of the file
+ */
+ case class EditorContext(
+ language: String
+ )
+}
diff --git a/src/main/scala/vscextension/commands.scala b/src/main/scala/vscextension/commands.scala
index abae9c7..1b7cd35 100644
--- a/src/main/scala/vscextension/commands.scala
+++ b/src/main/scala/vscextension/commands.scala
@@ -12,29 +12,22 @@ import facade.vscodeUtils.*
* This object registers all the commands in the extension.
*/
object commands {
- // Store all the commands here
+
+ /** Register all the commands in the extension.
+ *
+ * @param context
+ * the vscode extension context
+ */
def registerAllCommands(context: vscode.ExtensionContext) = {
- val cmds =
- Seq(
- ("extension.helloWorld", showHello)
- )
- // register the commands
- cmds foreach { (name, fun) =>
+ val allCommands =
+ functorcoder.actions.Commands.commandList
+ // register the commands
+ allCommands foreach { (name, fun) =>
context.pushDisposable(
vscode.commands.registerCommand(name, fun)
)
}
}
- /** Example command. VSCode commands can take an argument of any type, hence the `Any` here.
- *
- * @param arg
- * the argument (we don't use, but could be useful for other commands)
- */
- def showHello(arg: Any): Unit = {
- // show a message box when the command is executed in command palette
- // by typing hello
- vscode.window.showInformationMessage(s"Hello World! How are you ?")
- }
}
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 9679746..2e0d7e6 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -22,6 +22,10 @@ object extensionMain {
// register all commands
commands.registerAllCommands(context)
+ // show the status bar
+ val statusBarItem =
+ statusBar.createStatusBarItem(context)
+ // statusBarItem.text = "functorcoder ok"
// show the current language of the document
documentProps.showProps
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 54f2a66..176097a 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -2,9 +2,12 @@ package vscextension
import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
+import scala.scalajs.js.JSConverters._
+
import typings.vscode.mod as vscode
import facade.vscodeUtils.*
+import functorcoder.actions.Commands
/** Show a quick pick palette to select items in multiple steps
*
@@ -12,17 +15,13 @@ import facade.vscodeUtils.*
*/
object quickPick {
- def showQuickPick(): Unit = {
+ def showQuickPick(arg: Any): Unit = {
val items =
- js.Array("item1", "item2", "item3")
- js.Dynamic.literal(
- placeHolder = "pick one item"
- )
+ Commands.mainMenuItems.map(_._1).toJSArray
val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
vscode.window.createQuickPick()
- var steps = 0 // steps for the quick pick
quickPick.title = "Quick Pick"
quickPick.placeholder = "pick one item"
quickPick.totalSteps = 3
@@ -40,8 +39,6 @@ object quickPick {
quickPick.onDidChangeSelection { selection =>
println(s"selected: ${selection(0).label}")
- steps += 1
- quickPick.setStep(steps)
if (selection(0).label == "item1") {
println(s"selected: ${selection(0).label}")
@@ -58,9 +55,11 @@ object quickPick {
}
}
- /* quickPick.onDidHide({ () =>
- quickPick.dispose()
- }) */
+
+ quickPick.onDidHide({ _ =>
+ quickPick.hide()
+ })
+
quickPick.show()
}
}
diff --git a/src/main/scala/vscextension/statusBar.scala b/src/main/scala/vscextension/statusBar.scala
new file mode 100644
index 0000000..e88da94
--- /dev/null
+++ b/src/main/scala/vscextension/statusBar.scala
@@ -0,0 +1,22 @@
+package vscextension
+import typings.vscode.mod as vscode
+
+import vscextension.facade.vscodeUtils.*
+
+import functorcoder.actions.Commands
+object statusBar {
+
+ def createStatusBarItem(context: vscode.ExtensionContext) = {
+ val statusBarItem =
+ vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right)
+
+ val name = "functor"
+ statusBarItem.text = name
+ statusBarItem.name = name
+ statusBarItem.command = Commands.commandMenu
+ statusBarItem.show()
+
+ context.pushDisposable(statusBarItem.asInstanceOf[vscode.Disposable])
+ statusBarItem
+ }
+}
From f2b62ade80a81b02989151d866c1bfca4a5fa2d6 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 16:49:56 +0100
Subject: [PATCH 07/46] Add GitHub Actions workflow for building and publishing
VSIX package
---
.github/workflows/publish.yaml | 41 ++++++++++++++++++++++++++++++++++
1 file changed, 41 insertions(+)
create mode 100644 .github/workflows/publish.yaml
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
new file mode 100644
index 0000000..4664dba
--- /dev/null
+++ b/.github/workflows/publish.yaml
@@ -0,0 +1,41 @@
+name: Build and publish vsix package
+
+on:
+ push:
+
+env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+
+jobs:
+ run:
+ name: Build and Run
+ strategy:
+ matrix:
+ java-version: [17]
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout current branch (full)
+ uses: actions/checkout@v4
+ with:
+ fetch-depth: 0
+ # install java, sbt and node
+ - name: Setup Java
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: ${{ matrix.java-version }}
+ cache: sbt
+ - uses: sbt/setup-sbt@v1
+
+ - name: setup node
+ uses: actions/setup-node@v4
+
+ # install dependencies and build package
+ - name: compile Scala.js
+ run: sbt fastOptJS
+
+ - name: install dependencies
+ run: cp package.json out/ && npm
+
+ - name: package extension
+ run: cd out/ && npx vsce package
From 08d83db7ebc6b27f0a81d8f1c05cd7218ebef7c3 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 16:55:56 +0100
Subject: [PATCH 08/46] Update GitHub Actions workflow and build configuration
for debugging
---
.github/workflows/publish.yaml | 11 ++++-------
build.sbt | 6 +++++-
2 files changed, 9 insertions(+), 8 deletions(-)
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 4664dba..b2418fd 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -1,4 +1,4 @@
-name: Build and publish vsix package
+name: Release
on:
push:
@@ -8,7 +8,7 @@ env:
jobs:
run:
- name: Build and Run
+ name: Build and publish vsix package
strategy:
matrix:
java-version: [17]
@@ -31,11 +31,8 @@ jobs:
uses: actions/setup-node@v4
# install dependencies and build package
- - name: compile Scala.js
- run: sbt fastOptJS
-
- - name: install dependencies
- run: cp package.json out/ && npm
+ - name: compile Scala and build js to out/
+ run: sbt buildDebug
- name: package extension
run: cd out/ && npx vsce package
diff --git a/build.sbt b/build.sbt
index 1c647aa..1611207 100644
--- a/build.sbt
+++ b/build.sbt
@@ -4,6 +4,7 @@ import org.scalajs.linker.interface.{ModuleKind, ModuleInitializer, ModuleSplitS
val outdir = "out" // output directory for the extension
// open command in sbt
lazy val open = taskKey[Unit]("open vscode")
+lazy val buildDebug = taskKey[Unit]("build debug")
lazy val root = project
.in(file("."))
@@ -48,14 +49,17 @@ lazy val root = project
else Seq.empty), */
stIgnore ++= List( // don't generate types with scalablytyped
),
- open := openVSCodeTask().dependsOn(Compile / fastOptJS).value
+ open := openVSCodeTask().dependsOn(Compile / fastOptJS).value,
+ buildDebug := openVSCodeTask(openVscode = false).dependsOn(Compile / fastOptJS).value
// open := openVSCodeTask.dependsOn(Compile / fastOptJS / webpack).value,
// testFrameworks += new TestFramework("utest.runner.Framework")
// publishMarketplace := publishMarketplaceTask.dependsOn(fullOptJS in Compile).value
)
+
addCommandAlias("compile", ";fastOptJS")
addCommandAlias("dev", "~fastOptJS")
addCommandAlias("fix", ";scalafixEnable;scalafixAll;")
+// open, buildDebug are other commands added
def openVSCodeTask(openVscode: Boolean = true): Def.Initialize[Task[Unit]] =
Def
From 2d9435ea634fcc32f18a7838346397d8774de7ae Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 17:13:00 +0100
Subject: [PATCH 09/46] Enhance GitHub Actions workflow for packaging and
uploading VSIX; update package.json to include repository information
---
.github/workflows/publish.yaml | 31 +++++++++++++++++++++++++++----
package.json | 6 +++++-
2 files changed, 32 insertions(+), 5 deletions(-)
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index b2418fd..9143886 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -31,8 +31,31 @@ jobs:
uses: actions/setup-node@v4
# install dependencies and build package
- - name: compile Scala and build js to out/
- run: sbt buildDebug
+ - name: compile Scala and build js to out/ folder
+ run:
+ sbt buildDebug
+
+ - name: package extension inside out/ folder
+ run:
+ cd out/
+ ls -la
+ npx vsce package
+
+ - name: Get upload url
+ id: get_upload_url
+ run: |
+ URL=$(curl --silent "https://api.github.com/repos/doofin/functorcoder/releases/latest" | jq -r '.upload_url')
+ echo ::set-output name=UPLOAD_URL::$URL
+
+ - name: Upload VSIX package to github release
+ uses: actions/upload-release-asset@v1.0.1
+ env:
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ with:
+ upload_url: ${{ steps.get_upload_url.outputs.UPLOAD_URL }}
+ asset_path:
+ ./out/functorcoder-0.0.1.vsix
+ asset_name: |
+ functorcoder-0.0.1.vsix
+ asset_content_type: application/octet-stream
- - name: package extension
- run: cd out/ && npx vsce package
diff --git a/package.json b/package.json
index f475601..1742564 100644
--- a/package.json
+++ b/package.json
@@ -2,6 +2,10 @@
"name": "functorcoder",
"displayName": "functorcoder",
"description": "an ai coding assistant",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/doofin/functorcoder/"
+ },
"version": "0.0.1",
"publisher": "functorcoder.com",
"categories": [
@@ -10,7 +14,7 @@
"activationEvents": [
"*"
],
- "main": "./out/extension",
+ "main": "./extension",
"engines": {
"vscode": "^1.84.0"
},
From 20bd9b7e7080cf3591afc88eef298317cf6cce6f Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 17:27:47 +0100
Subject: [PATCH 10/46] Fix workflow script to chain commands for packaging
extension in GitHub Actions
---
.github/workflows/publish.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 9143886..8565dd7 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -37,8 +37,8 @@ jobs:
- name: package extension inside out/ folder
run:
- cd out/
- ls -la
+ cd out/ &&
+ ls -la &&
npx vsce package
- name: Get upload url
From 801f8e1cf80595c32a57a54ef5cd35818a365f3d Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 17:38:09 +0100
Subject: [PATCH 11/46] Update GitHub Actions workflow to trigger on release
branch and improve VSIX upload process
---
.github/workflows/publish.yaml | 33 ++++++++++++++++-----------------
1 file changed, 16 insertions(+), 17 deletions(-)
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 8565dd7..cb82abd 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -1,7 +1,10 @@
name: Release
+# trigger on git push for release branch
on:
- push:
+ push:
+ branches:
+ - release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
@@ -14,12 +17,15 @@ jobs:
java-version: [17]
runs-on: ubuntu-latest
steps:
- - name: Checkout current branch (full)
+ - name: Checkout current branch
uses: actions/checkout@v4
with:
- fetch-depth: 0
+ fetch-depth: 0
+ # cache sbt dependencies
+ - uses: coursier/cache-action@v6
+
# install java, sbt and node
- - name: Setup Java
+ - name: Setup Java and sbt
uses: actions/setup-java@v4
with:
distribution: temurin
@@ -36,9 +42,9 @@ jobs:
sbt buildDebug
- name: package extension inside out/ folder
- run:
- cd out/ &&
- ls -la &&
+ run: |
+ cd out/
+ ls -la
npx vsce package
- name: Get upload url
@@ -48,14 +54,7 @@ jobs:
echo ::set-output name=UPLOAD_URL::$URL
- name: Upload VSIX package to github release
- uses: actions/upload-release-asset@v1.0.1
- env:
- GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
+ uses: softprops/action-gh-release@v2
+ # if: startsWith(github.ref, 'refs/tags/')
with:
- upload_url: ${{ steps.get_upload_url.outputs.UPLOAD_URL }}
- asset_path:
- ./out/functorcoder-0.0.1.vsix
- asset_name: |
- functorcoder-0.0.1.vsix
- asset_content_type: application/octet-stream
-
+ files: out/functorcoder-0.0.1.vsix
\ No newline at end of file
From f09668568914da222e324335d6ba1d4b52724a73 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 17:39:26 +0100
Subject: [PATCH 12/46] Comment out branch trigger in GitHub Actions workflow
for release
---
.github/workflows/publish.yaml | 4 ++--
1 file changed, 2 insertions(+), 2 deletions(-)
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index cb82abd..7c3b2e2 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -3,8 +3,8 @@ name: Release
# trigger on git push for release branch
on:
push:
- branches:
- - release
+ # branches:
+ # - release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
From 3f45ac8234634a4b23e22f2e65b742567207ebe3 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 12 Feb 2025 21:21:29 +0100
Subject: [PATCH 13/46] Refactor GitHub Actions workflows and update
configuration handling in the extension
---
.github/workflows/publish.yaml | 5 ++-
.github/workflows/scala.yml | 7 +++-
.../functorcoder/editorUI/diffEdit.scala | 33 ++++++++++++++++
.../functorcoder/editorUI/editorConfig.scala | 32 ----------------
src/main/scala/vscextension/diffEdit.scala | 17 +++++++++
.../scala/vscextension/extensionMain.scala | 3 +-
.../vscextension/inlineCompletions.scala | 8 +++-
src/main/scala/vscextension/settings.scala | 38 +++++++++++++++++++
8 files changed, 103 insertions(+), 40 deletions(-)
create mode 100644 src/main/scala/functorcoder/editorUI/diffEdit.scala
create mode 100644 src/main/scala/vscextension/diffEdit.scala
create mode 100644 src/main/scala/vscextension/settings.scala
diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml
index 7c3b2e2..9e4b853 100644
--- a/.github/workflows/publish.yaml
+++ b/.github/workflows/publish.yaml
@@ -2,9 +2,10 @@ name: Release
# trigger on git push for release branch
on:
+ workflow_dispatch: # allow manual trigger
push:
- # branches:
- # - release
+ branches:
+ - release
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
diff --git a/.github/workflows/scala.yml b/.github/workflows/scala.yml
index bde01d3..42c95c1 100644
--- a/.github/workflows/scala.yml
+++ b/.github/workflows/scala.yml
@@ -1,4 +1,4 @@
-name: Continuous Integration
+name: CI
on:
push:
@@ -8,7 +8,7 @@ env:
jobs:
run:
- name: Build and Run
+ name: Compile
strategy:
matrix:
java-version: [17]
@@ -26,6 +26,9 @@ jobs:
java-version: ${{ matrix.java-version }}
cache: sbt
+ # cache sbt dependencies
+ - uses: coursier/cache-action@v6
+
- uses: sbt/setup-sbt@v1
- name: compile
diff --git a/src/main/scala/functorcoder/editorUI/diffEdit.scala b/src/main/scala/functorcoder/editorUI/diffEdit.scala
new file mode 100644
index 0000000..fde364b
--- /dev/null
+++ b/src/main/scala/functorcoder/editorUI/diffEdit.scala
@@ -0,0 +1,33 @@
+package functorcoder.editorUI
+
+/** This abstract the editor behavior for inline editing
+ *
+ * Scenario: A new version of the file is shown to the user in the editor with the changes highlighted,
+ *
+ * and the user can choose to accept or reject the changes.
+ */
+object diffEdit {
+
+ case class CursorPosition(
+ line: Int,
+ character: Int
+ )
+
+ /** The result of the diff operation
+ *
+ * @param oldText
+ * the old text
+ * @param newText
+ * the new text
+ * @param cursorPosition
+ * the cursor position
+ * @param difference
+ * the difference between the old and new text
+ */
+ case class DiffResult(
+ oldText: String,
+ newText: String,
+ cursorPosition: CursorPosition,
+ difference: Seq[String]
+ )
+}
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index 26820ed..561b193 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -1,39 +1,7 @@
package functorcoder.editorUI
-import typings.vscode.mod as vscode
// https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
object editorConfig {
case class Config(openaiApiKey: String, openaiUrl: String)
- /** read the configuration from the vscode settings.json
- *
- * @return
- * the configuration object
- */
- def readConfig() = {
- val config = vscode.workspace.getConfiguration("functorcoder")
- val openaiApiKey =
- config.getStringOrEmpty("openaiApiKey")
-
- val openaiUrl =
- config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
-
- Config(openaiApiKey, openaiUrl)
- }
-
- extension (config: vscode.WorkspaceConfiguration) {
-
- /** get a string from the configuration or return an empty string if default is not provided
- *
- * @param str
- * the string to get
- * @param default
- * the default value
- * @return
- * the string or an empty string
- */
- def getStringOrEmpty(key: String, default: String = "") = {
- config.get[String](key).toOption.getOrElse(default)
- }
- }
}
diff --git a/src/main/scala/vscextension/diffEdit.scala b/src/main/scala/vscextension/diffEdit.scala
new file mode 100644
index 0000000..55ecc1a
--- /dev/null
+++ b/src/main/scala/vscextension/diffEdit.scala
@@ -0,0 +1,17 @@
+package vscextension
+
+/** This object provides the diff edits for the vscode extension.
+ *
+ * currently, inline edit api is locked by vscode/microsoft as an insider feature.
+ *
+ * you need modify the product.json file to enable "proposed" api.
+ *
+ * https://github.com/microsoft/vscode/issues/190239
+ * https://stackoverflow.com/questions/77202394/what-vs-code-api-can-i-use-to-create-an-in-editor-chat-box-like-in-github-copilo
+ * https://stackoverflow.com/questions/76783624/vscode-extension-how-can-i-add-custom-ui-inside-the-editor
+ *
+ * https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts
+ */
+object diffEdit {
+// new MappedEditsProvider
+}
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 2e0d7e6..b997165 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -6,7 +6,6 @@ import scala.scalajs.js.annotation.JSExportTopLevel
import typings.vscode.mod as vscode
import facade.vscodeUtils.*
-import functorcoder.editorUI.editorConfig
object extensionMain {
@@ -17,7 +16,7 @@ object extensionMain {
showMessageAndLog("congrats, your scala.js vscode extension is loaded")
vscode.workspace.rootPath.getOrElse("")
- val cfg = editorConfig.readConfig()
+ val cfg = settings.readConfig()
showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
commands.registerAllCommands(context)
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index 81ef5bb..409b1ed 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -7,7 +7,6 @@ import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.Promise
-import functorcoder.editorUI.editorConfig
/** demonstrates how to provide inline completions in the editor. like the github copilot
* https://github.com/microsoft/vscode-extension-samples/tree/main/inline-completions
@@ -15,8 +14,13 @@ import functorcoder.editorUI.editorConfig
*/
object inlineCompletions {
+ /** Creates an inline completion item provider for Visual Studio Code.
+ *
+ * @return
+ * An instance of `vscode.InlineCompletionItemProvider`.
+ */
def createCompletionProvider(): vscode.InlineCompletionItemProvider = {
- val cfg = editorConfig.readConfig()
+ val cfg = settings.readConfig()
val llm = functorcoder.llm.llmMain.llmAgent(cfg)
new vscode.InlineCompletionItemProvider {
diff --git a/src/main/scala/vscextension/settings.scala b/src/main/scala/vscextension/settings.scala
new file mode 100644
index 0000000..e126bc6
--- /dev/null
+++ b/src/main/scala/vscextension/settings.scala
@@ -0,0 +1,38 @@
+package vscextension
+
+import typings.vscode.mod as vscode
+import functorcoder.editorUI.editorConfig.Config
+
+object settings {
+
+ /** read the configuration from the vscode settings.json
+ *
+ * https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
+ */
+ def readConfig() = {
+ val config = vscode.workspace.getConfiguration("functorcoder")
+ val openaiApiKey =
+ config.getStringOrEmpty("openaiApiKey")
+
+ val openaiUrl =
+ config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
+
+ Config(openaiApiKey, openaiUrl)
+ }
+
+ extension (config: vscode.WorkspaceConfiguration) {
+
+ /** get a string from the configuration or return an empty string if default is not provided
+ *
+ * @param str
+ * the string to get
+ * @param default
+ * the default value
+ * @return
+ * the string or an empty string
+ */
+ def getStringOrEmpty(key: String, default: String = "") = {
+ config.get[String](key).toOption.getOrElse(default)
+ }
+ }
+}
From e2bd43021ca3ee2b5b45a68e83da354141d6ca4f Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 16 Feb 2025 00:44:07 +0100
Subject: [PATCH 14/46] Enhance VSCode extension with inline editing
capabilities and LLM integration; refactor code structure and improve
configuration handling
---
build.sbt | 7 +-
.../scala/functorcoder/actions/Commands.scala | 19 ++-
.../functorcoder/editorUI/diffEdit.scala | 46 ++++-
src/main/scala/functorcoder/llm/llmMain.scala | 33 +++-
.../scala/functorcoder/llm/llmPrompt.scala | 44 ++++-
.../scala/functorcoder/llm/wk.worksheet.sc | 20 +++
.../scala/functorcoder/types/editorCtx.scala | 7 +
src/main/scala/vscextension/CodeActions.scala | 160 +++++++++++-------
src/main/scala/vscextension/diffEdit.scala | 17 --
.../scala/vscextension/diffInlineEdit.scala | 34 ++++
.../scala/vscextension/extensionMain.scala | 6 +-
.../vscextension/inlineCompletions.scala | 37 ++--
src/main/scala/vscextension/settings.scala | 7 +-
13 files changed, 314 insertions(+), 123 deletions(-)
create mode 100644 src/main/scala/functorcoder/llm/wk.worksheet.sc
delete mode 100644 src/main/scala/vscextension/diffEdit.scala
create mode 100644 src/main/scala/vscextension/diffInlineEdit.scala
diff --git a/build.sbt b/build.sbt
index 1611207..355b570 100644
--- a/build.sbt
+++ b/build.sbt
@@ -22,7 +22,8 @@ lazy val root = project
scalaVersion := "3.3.4",
// warn unused imports and vars
scalacOptions ++= Seq(
- "-Wunused:all"
+ "-Wunused:all",
+ "-no-indent"
),
// check if it is running in test
// testOptions += Tests.Setup(_ => sys.props("testing") = "true"),
@@ -80,8 +81,10 @@ def openVSCodeTask(openVscode: Boolean = true): Def.Initialize[Task[Unit]] =
}
// launch vscode
if (openVscode) {
+ val extenPath = s"${path}/${outdir}"
println("\u001b[33m" + "[opening] vscode" + "\u001b[0m")
- s"code --extensionDevelopmentPath=$path" ! log
+ println("\u001b[33m" + s"with extensionDevelopmentPath=${extenPath}" + "\u001b[0m")
+ s"code --extensionDevelopmentPath=$extenPath" ! log
}
()
}
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index f6a2c83..6366e97 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -1,4 +1,5 @@
package functorcoder.actions
+import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
@@ -6,6 +7,9 @@ import scala.collection.immutable
import scala.scalajs.js
import vscextension.quickPick
+import vscextension.facade.vscodeUtils.showMessageAndLog
+import scala.concurrent.Future
+import functorcoder.types.editorCtx.codeActionParam
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
@@ -13,14 +17,27 @@ object Commands {
type CommandT = Any => Unit
// all the commands here
val commandMenu = "functorcoder.menu"
+ val commandAddDocumentation = "functorcoder.addDocumentation"
val commandList: Seq[(String, CommandT)] =
Seq(
- (commandMenu, quickPick.showQuickPick)
+ (commandMenu, quickPick.showQuickPick),
+ (commandAddDocumentation, addDocumentation)
)
// the main menu items
val mainMenuItems: Seq[(String, () => Unit)] = Seq(
"create files" -> { () => println("create files") }
)
+
+ def addDocumentation(arg: Any) = {
+ val param =
+ arg.asInstanceOf[codeActionParam[Future[String]]]
+ val llmResponse = param.param
+ llmResponse.foreach { response =>
+ showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
+ }
+
+ // showMessageAndLog("add documentation: " + s"${dyn.uri}, ${dyn.range}, ${dyn.llmResponse}")
+ }
}
diff --git a/src/main/scala/functorcoder/editorUI/diffEdit.scala b/src/main/scala/functorcoder/editorUI/diffEdit.scala
index fde364b..32f4a0a 100644
--- a/src/main/scala/functorcoder/editorUI/diffEdit.scala
+++ b/src/main/scala/functorcoder/editorUI/diffEdit.scala
@@ -1,10 +1,14 @@
package functorcoder.editorUI
+import scala.concurrent.ExecutionContext.Implicits.global
+
+import functorcoder.llm.llmMain
+import functorcoder.llm.llmPrompt
+
/** This abstract the editor behavior for inline editing
*
- * Scenario: A new version of the file is shown to the user in the editor with the changes highlighted,
- *
- * and the user can choose to accept or reject the changes.
+ * Scenario: user wants to edit a selected snippet of code in the editor, the coding assistant will provide a modified
+ * version of the snippet with the changes highlighted, and the user can choose to accept or reject the changes.
*/
object diffEdit {
@@ -13,6 +17,12 @@ object diffEdit {
character: Int
)
+ case class DiffRequest(
+ oldText: String,
+ cursorPosition: CursorPosition,
+ task: String // the task to perform
+ )
+
/** The result of the diff operation
*
* @param oldText
@@ -27,7 +37,33 @@ object diffEdit {
case class DiffResult(
oldText: String,
newText: String,
- cursorPosition: CursorPosition,
- difference: Seq[String]
+ cursorPosition: CursorPosition
+ // difference: Seq[String]
)
+
+ /** action to modify the code, like adding new code
+ *
+ * @param llmAgent
+ * the agent to perform the diff operation
+ * @param diffReq
+ * the diff request
+ * @return
+ * the result of the diff operation
+ */
+ def diff(llmAgent: llmMain.llmAgent, diffReq: DiffRequest) = {
+ val prompt = llmPrompt.Modification(
+ code = diffReq.oldText,
+ taskRequirement = ""
+ )
+
+ val llmResponse = llmAgent.sendPrompt(prompt)
+
+ llmResponse.map(txt =>
+ DiffResult(
+ oldText = diffReq.oldText,
+ newText = txt,
+ cursorPosition = diffReq.cursorPosition
+ )
+ )
+ }
}
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index 21e8c98..d10bbba 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -12,6 +12,7 @@ import scala.scalajs.js.Thenable.Implicits.*
import scala.concurrent.Future
import functorcoder.editorUI.editorConfig
+import cats.syntax.show
/** large language model (LLM) AI main
*
@@ -27,16 +28,21 @@ object llmMain {
* completion prompt object
* @return
*/
- def getCompletionPrompt(completionPrompt: llmPrompt.Completion) = {
- openaiReq
+ def prompt2str(inputPrompt: llmPrompt.Prompt) = {
+ showMessageAndLog(s"prompt: ${inputPrompt}")
+ showMessageAndLog(s"prompt assistant: ${inputPrompt.getAssistantMessage}")
+
+ val openAiRequest = openaiReq
.OpenAiRequest(
List(
- openaiReq.Message(roles.user, completionPrompt.codeWithHole),
- openaiReq.Message(roles.system, completionPrompt.assistantMessage)
+ openaiReq.Message(roles.user, inputPrompt.generatePrompt),
+ openaiReq.Message(roles.system, inputPrompt.getAssistantMessage)
),
openaiReq.models.gpt4o
)
- .toJson
+
+ showMessageAndLog(s"openai request: ${openAiRequest}")
+ openAiRequest.toJson
}
case class llmAgent(editorCfg: editorConfig.Config) {
@@ -51,10 +57,10 @@ object llmMain {
*
* so we will call this function with the hole "{{FILL_HERE}}" you insert it in the code
*/
- def getCodeCompletion(holeToCode: String => String) = {
- val requestStr = getCompletionPrompt(
- llmPrompt
- .Completion(holeToCode("{{FILL_HERE}}"))
+ def sendPrompt(input: llmPrompt.Prompt) = {
+
+ val requestStr = prompt2str(
+ input
)
val requestOptions = getRequestOptions(requestStr)
@@ -76,6 +82,15 @@ object llmMain {
}
}
+ /** get the response text from ai api, only the content of the first choice
+ *
+ * it parses the response json and returns the first choice
+ *
+ * @param responseFuture
+ * the response future
+ * @return
+ * the response text
+ */
private def getResponseText(responseFuture: Future[nodeFetch.Response]) = {
for {
res <- responseFuture
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index f19fb06..d7bff12 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -5,8 +5,13 @@ package functorcoder.llm
* for completion, code generation, etc.
*/
object llmPrompt {
+ // trait will have undefined value
+ sealed abstract class Prompt(val assistantMsg: String) {
+ def generatePrompt: String
+ def getAssistantMessage: String = assistantMsg
+ }
- /** a configuration for code completion
+ /** code completion prompt
*
* https://github.com/continuedev/continue/blob/main/core/autocomplete/templating/AutocompleteTemplate.ts
*
@@ -26,8 +31,8 @@ object llmPrompt {
case class Completion(
codeWithHole: String, // code with a hole to fill like {{FILL_HERE}}
// taskRequirement: String, // like "Fill the {{FILL_HERE}} hole."
- assistantMessage: String = prompts.prompt1
- ) {
+ assistantMessage: String = promptText.prompt1
+ ) extends Prompt(assistantMessage) {
def generatePrompt = {
// shall return a string wrapped with
// s"""
@@ -40,13 +45,38 @@ object llmPrompt {
*/
codeWithHole
}
+
+ }
+
+ /** modify code snippet
+ *
+ * @param code
+ * code snippet
+ * @param taskRequirement
+ * like "Fill the {{FILL_HERE}} hole."
+ * @param assistantMessage
+ * like "always give scala code examples."
+ */
+ case class Modification(
+ code: String,
+ taskRequirement: String,
+ assistantMessage: String = promptText.promptTask
+ ) extends Prompt(assistantMessage) {
+ def generatePrompt = {
+ s"""
+ |${code}
+ |
+ |TASK: ${taskRequirement}
+ |""".stripMargin
+ }
}
/** prompts engineering
*
* more like art than science. just try different prompts and see what works best
*/
- object prompts {
+ object promptText {
+ val hole = "{{FILL_HERE}}"
val prompt1 =
"You are a code or text autocompletion assistant. " +
"In the provided input, missing code or text are marked as '{{FILL_HERE}}'. " +
@@ -58,11 +88,15 @@ object llmPrompt {
"{{FILL_HERE}} in the string, " +
"your task is to replace this hole with your reply." +
"you only return the string for the hole with indentation, without any quotes"
+
+ val promptTask =
+ "You are given a text or code snippet wrapped in a tag and a TASK requirement. " +
+ "You are going to return the new snippet according to the TASK requirement. "
}
}
/* example:
-
+
function sum_evens(lim) {
var sum = 0;
for (var i = 0; i < lim; ++i) {
diff --git a/src/main/scala/functorcoder/llm/wk.worksheet.sc b/src/main/scala/functorcoder/llm/wk.worksheet.sc
new file mode 100644
index 0000000..6bccc7e
--- /dev/null
+++ b/src/main/scala/functorcoder/llm/wk.worksheet.sc
@@ -0,0 +1,20 @@
+import functorcoder.llm.llmPrompt
+import functorcoder.llm.llmPrompt.Prompt
+import fansi.Str
+val Modification = llmPrompt
+ .Modification(code = "val x = 1", taskRequirement = "add documentation")
+
+Modification.asInstanceOf[Prompt]
+
+sealed trait trait1(val param1: String) {
+ // def method1: String = param1
+}
+
+case class class1(override val param1: String = "s1") extends trait1(param1)
+
+val c1 = class1()
+
+def m1(t1: trait1) = {
+ println(s"t1 param1: ${t1.param1}")
+}
+// c1.method1
diff --git a/src/main/scala/functorcoder/types/editorCtx.scala b/src/main/scala/functorcoder/types/editorCtx.scala
index 575d2e4..a97b253 100644
--- a/src/main/scala/functorcoder/types/editorCtx.scala
+++ b/src/main/scala/functorcoder/types/editorCtx.scala
@@ -1,4 +1,5 @@
package functorcoder.types
+import scala.scalajs.js
object editorCtx {
@@ -10,4 +11,10 @@ object editorCtx {
case class EditorContext(
language: String
)
+
+ class codeActionParam[T](
+ val documentUri: String, //
+ val range: typings.vscode.mod.Selection,
+ val param: T
+ ) extends js.Object
}
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 2c75bac..8037a53 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -1,8 +1,16 @@
package vscextension
import typings.vscode.mod as vscode
import scala.scalajs.js
+import scala.concurrent.ExecutionContext.Implicits.global
+import scala.scalajs.js.JSConverters.JSRichFutureNonThenable
import facade.vscodeUtils.*
+import functorcoder.llm.llmMain.llmAgent
+import functorcoder.llm.llmPrompt
+import cats.syntax.show
+import scala.concurrent.duration.Duration
+import scala.concurrent.Await
+import scala.concurrent.Future
/** Code actions are commands provided at the cursor in the editor, so users can
*
@@ -12,90 +20,122 @@ import facade.vscodeUtils.*
*/
object CodeActions {
- def registerCodeActions(context: vscode.ExtensionContext) = {
+ def registerCodeActions(context: vscode.ExtensionContext, llm: llmAgent) = {
// https://scalablytyped.org/docs/encoding#jsnative
// we need to manually create the js object and cast it
val mActionProvider =
new js.Object {
+ def createCodeAction(
+ document: vscode.TextDocument,
+ range: vscode.Selection,
+ context: vscode.CodeActionContext
+ ) = {
+ val selectedCode = document.getText(range)
+ val llmResponse =
+ llm.sendPrompt(
+ llmPrompt.Modification(
+ code = selectedCode, //
+ taskRequirement = "add documentation"
+ )
+ )
+
+ val editor = new vscode.WorkspaceEdit() {
+ // linking issue from compiler
+ // llmResponse.wait()
+ // val response = Await.result(llmResponse, Duration.Inf)
+ // // llmResponse.toJSPromise.wait()
+ // showMessageAndLog("llm response: " + response)
+ // insert(
+ // uri = document.uri,
+ // position = range.start,
+ // newText = response
+ // )
+ }
+
+ llmResponse.foreach { response =>
+ showMessageAndLog("llm response: " + response)
+ // does not work since it is not in the same thread?
+ editor.insert(
+ uri = document.uri,
+ position = range.start,
+ newText = response
+ )
+ }
+
+ val fix1 =
+ new vscode.CodeAction(
+ title = "generate documentation",
+ kind = vscode.CodeActionKind.QuickFix
+ ) {
+ isPreferred = true // show it first
+ // should invoke a command to perform the action
+ import functorcoder.types.editorCtx.*
+ val args: codeActionParam[Future[String]] = new codeActionParam(
+ document.uri.toString(),
+ range,
+ llmResponse
+ )
+
+ command = vscode
+ .Command(
+ command = functorcoder.actions.Commands.commandAddDocumentation, //
+ title = "add documentation" //
+ )
+ .setArguments(js.Array(args))
+
+ // edit = editor
+ // optional command to run when the code action is selected
+ // command = ..
+ }
+ // can return array or promise of array
+
+ js.Array(fix1)
+
+ // the code action for learn more
+ }
+
+ // override def provideCodeActions
def provideCodeActions(
document: vscode.TextDocument,
range: vscode.Selection,
context: vscode.CodeActionContext,
token: vscode.CancellationToken
- ): js.Array[vscode.CodeAction] = {
+ ): vscode.ProviderResult[js.Array[vscode.CodeAction]] = {
// check who triggers the code action, since vscode may trigger it automatically
- context.triggerKind match {
+ val res = context.triggerKind match {
case vscode.CodeActionTriggerKind.Invoke =>
// triggered by user
+
showMessageAndLog("selected code: " + document.getText(range))
+ createCodeAction(document, range, context)
+
case _ =>
+ // vscode triggered it automatically, just return an empty array
+ js.Array()
+
}
- createCodeAction(document, range, context)
- // show an underline for compiler issues
- /* context.diagnostics.map { diagnostic =>
- val codeAction = createCodeAction()
- codeAction
- } */
+ res.asInstanceOf[vscode.ProviderResult[js.Array[vscode.CodeAction]]]
+
}
+ // cast the object to the required type
}.asInstanceOf[vscode.CodeActionProvider[vscode.CodeAction]]
- val registration: vscode.Disposable = vscode.languages.registerCodeActionsProvider(
- selector = "*",
- provider = mActionProvider,
- metadata = vscode
- .CodeActionProviderMetadata()
- .setProvidedCodeActionKinds(
- js.Array(vscode.CodeActionKind.QuickFix)
- )
- )
+ val registration: vscode.Disposable =
+ vscode.languages.registerCodeActionsProvider(
+ selector = "*",
+ provider = mActionProvider,
+ metadata = vscode
+ .CodeActionProviderMetadata()
+ .setProvidedCodeActionKinds(
+ js.Array(vscode.CodeActionKind.QuickFix)
+ )
+ )
context.pushDisposable(registration)
showMessageAndLog("registered code actions")
}
- def createCodeAction(document: vscode.TextDocument, range: vscode.Selection, context: vscode.CodeActionContext) = {
-
- // create quick fix action item
- val codeActionFix1 =
- new vscode.CodeAction(
- title = "My Code Action- replace with hello string",
- kind = vscode.CodeActionKind.QuickFix
- ) {
- isPreferred = true
- edit = new vscode.WorkspaceEdit() {
- replace(
- uri = document.uri,
- range = range,
- newText = "hello"
- )
- }
- // optional command to run when the code action is selected
- // command = vscode
- // .Command(
- // title = "My Code Action",
- // command = "myextension.myCodeAction.replaceWithHello"
- // )
- // .setTooltip("This is my code action")
- }
-
- // the code action for learn more
- val suggestedActionMore =
- new vscode.CodeAction(
- title = "learn more",
- kind = vscode.CodeActionKind.Empty
- ) {
- command = vscode
- .Command(
- title = "My Code Action",
- command = "myextension.myCodeAction.learnMore"
- )
- .setTooltip("This will show you more information")
- }
-
- js.Array(codeActionFix1, suggestedActionMore)
- }
-
}
diff --git a/src/main/scala/vscextension/diffEdit.scala b/src/main/scala/vscextension/diffEdit.scala
deleted file mode 100644
index 55ecc1a..0000000
--- a/src/main/scala/vscextension/diffEdit.scala
+++ /dev/null
@@ -1,17 +0,0 @@
-package vscextension
-
-/** This object provides the diff edits for the vscode extension.
- *
- * currently, inline edit api is locked by vscode/microsoft as an insider feature.
- *
- * you need modify the product.json file to enable "proposed" api.
- *
- * https://github.com/microsoft/vscode/issues/190239
- * https://stackoverflow.com/questions/77202394/what-vs-code-api-can-i-use-to-create-an-in-editor-chat-box-like-in-github-copilo
- * https://stackoverflow.com/questions/76783624/vscode-extension-how-can-i-add-custom-ui-inside-the-editor
- *
- * https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts
- */
-object diffEdit {
-// new MappedEditsProvider
-}
diff --git a/src/main/scala/vscextension/diffInlineEdit.scala b/src/main/scala/vscextension/diffInlineEdit.scala
new file mode 100644
index 0000000..dfa4962
--- /dev/null
+++ b/src/main/scala/vscextension/diffInlineEdit.scala
@@ -0,0 +1,34 @@
+package vscextension
+
+import functorcoder.editorUI.diffEdit.*
+
+/** This object provides the diff edits for the vscode extension.
+ *
+ * currently, inline edit api is locked by vscode/microsoft as an insider feature.
+ *
+ * to overcome this, modify the product.json file to enable "proposed" api.
+ *
+ * https://github.com/microsoft/vscode/issues/190239
+ * https://stackoverflow.com/questions/77202394/what-vs-code-api-can-i-use-to-create-an-in-editor-chat-box-like-in-github-copilo
+ * https://stackoverflow.com/questions/76783624/vscode-extension-how-can-i-add-custom-ui-inside-the-editor
+ *
+ * https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts
+ *
+ * related: source for vscode
+ * https://github.com/microsoft/vscode/blob/main/src/vs/workbench/services/extensions/browser/extensionService.ts#L226
+ *
+ * the product.json location has changed several times. /opt/visual-studio-code/resources/app/product.json
+ *
+ * use `strace -f code 2>&1 | grep product` to find the location of the product.json
+ *
+ * for more info: https://github.com/VSCodium/vscodium/blob/master/docs/index.md
+ */
+object diffInlineEdit {
+
+ /** create a diff edit in the editor
+ *
+ * @param diffResult
+ * the diff result
+ */
+ def createDiffEdit(diffResult: DiffResult): Unit = {}
+}
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index b997165..bb67f0b 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -17,6 +17,8 @@ object extensionMain {
vscode.workspace.rootPath.getOrElse("")
val cfg = settings.readConfig()
+ val llm = functorcoder.llm.llmMain.llmAgent(cfg)
+
showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
commands.registerAllCommands(context)
@@ -29,13 +31,13 @@ object extensionMain {
documentProps.showProps
// register inline completions like github copilot
- inlineCompletions.registerInlineCompletions()
+ inlineCompletions.registerInlineCompletions(llm)
// quick pick palette, like command palette
// quickPick.showQuickPick()
// code actions like quick fixes
- CodeActions.registerCodeActions(context)
+ CodeActions.registerCodeActions(context, llm)
// functorcoder.llm.llmAIMain.test
// file operations
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index 409b1ed..be246cc 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -7,6 +7,9 @@ import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.Promise
+import functorcoder.llm.llmPrompt
+import functorcoder.llm.llmMain.llmAgent
+import vscextension.facade.vscodeUtils.showMessageAndLog
/** demonstrates how to provide inline completions in the editor. like the github copilot
* https://github.com/microsoft/vscode-extension-samples/tree/main/inline-completions
@@ -14,16 +17,8 @@ import scala.scalajs.js.Promise
*/
object inlineCompletions {
- /** Creates an inline completion item provider for Visual Studio Code.
- *
- * @return
- * An instance of `vscode.InlineCompletionItemProvider`.
- */
- def createCompletionProvider(): vscode.InlineCompletionItemProvider = {
- val cfg = settings.readConfig()
- val llm = functorcoder.llm.llmMain.llmAgent(cfg)
-
- new vscode.InlineCompletionItemProvider {
+ def registerInlineCompletions(llm: llmAgent) = {
+ val mCompletionProvider = new vscode.InlineCompletionItemProvider {
override def provideInlineCompletionItems(
document: vscode.TextDocument, // the current document
position: vscode.Position, // the position of the cursor
@@ -33,21 +28,23 @@ object inlineCompletions {
val codeBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position))
val codeAfter = document.getText(new vscode.Range(position, document.positionAt(document.getText().length)))
- val r = llm.getCodeCompletion { hole =>
- val prompt = s"$codeBefore$hole$codeAfter"
- // showMessageAndLog(s"prompt: $prompt")
- prompt
- }
+
+ val prompt = llmPrompt
+ .Completion(codeWithHole = s"$codeBefore${llmPrompt.promptText.hole}$codeAfter")
+
+ // assistantMessage: String = promptText.prompt1
+ val promptResponseF = llm.sendPrompt(prompt)
val providerResultF: Promise[scala.scalajs.js.Array[vscode.InlineCompletionItem]] =
- r.map(completionText =>
+ promptResponseF.map { completionText =>
+ showMessageAndLog(s"completionText: $completionText")
js.Array(
new vscode.InlineCompletionItem(
insertText = completionText, // text to insert
range = new vscode.Range(position, position)
)
)
- ).toJSPromise
+ }.toJSPromise
providerResultF.asInstanceOf[typings.vscode.mod.ProviderResult[
scala.scalajs.js.Array[typings.vscode.mod.InlineCompletionItem] | typings.vscode.mod.InlineCompletionList
@@ -55,9 +52,7 @@ object inlineCompletions {
}
}
- }
-
- def registerInlineCompletions() = {
- vscode.languages.registerInlineCompletionItemProvider(selector = "*", provider = createCompletionProvider())
+ vscode.languages
+ .registerInlineCompletionItemProvider(selector = "*", provider = mCompletionProvider)
}
}
diff --git a/src/main/scala/vscextension/settings.scala b/src/main/scala/vscextension/settings.scala
index e126bc6..8350f6d 100644
--- a/src/main/scala/vscextension/settings.scala
+++ b/src/main/scala/vscextension/settings.scala
@@ -32,7 +32,12 @@ object settings {
* the string or an empty string
*/
def getStringOrEmpty(key: String, default: String = "") = {
- config.get[String](key).toOption.getOrElse(default)
+ // if the key is found but the value is empty, return the default
+
+ config.get[String](key).toOption match {
+ case None => default
+ case Some(value) => if (value.isEmpty) default else value
+ }
}
}
}
From 4156e1556109ee966e2f5b59a071d6c3e6f60908 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 16 Feb 2025 19:02:34 +0100
Subject: [PATCH 15/46] Refactor command handling to improve documentation
generation; add language detection and streamline code action prompts
---
.../scala/functorcoder/actions/Commands.scala | 23 ++++++++++++++++
.../scala/functorcoder/llm/llmPrompt.scala | 7 +++++
src/main/scala/vscextension/CodeActions.scala | 27 ++-----------------
.../scala/vscextension/documentProps.scala | 16 ++++++-----
4 files changed, 42 insertions(+), 31 deletions(-)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 6366e97..477ba73 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -10,6 +10,7 @@ import vscextension.quickPick
import vscextension.facade.vscodeUtils.showMessageAndLog
import scala.concurrent.Future
import functorcoder.types.editorCtx.codeActionParam
+import typings.std.stdStrings.ins
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
@@ -19,6 +20,7 @@ object Commands {
val commandMenu = "functorcoder.menu"
val commandAddDocumentation = "functorcoder.addDocumentation"
+ // list of all commands to be registered
val commandList: Seq[(String, CommandT)] =
Seq(
(commandMenu, quickPick.showQuickPick),
@@ -30,12 +32,33 @@ object Commands {
"create files" -> { () => println("create files") }
)
+ // individual command handlers
def addDocumentation(arg: Any) = {
val param =
arg.asInstanceOf[codeActionParam[Future[String]]]
val llmResponse = param.param
llmResponse.foreach { response =>
showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
+ // apply the changes to the document
+ vscode.window.activeTextEditor.toOption match {
+ case None =>
+ showMessageAndLog("no active editor!")
+ case Some(ed) =>
+ ed.insertSnippet(
+ new vscode.SnippetString("\n" + response), //
+ param.range.start
+ )
+ }
+
+ // vscode.workspace.applyEdit(
+ // new vscode.WorkspaceEdit {
+ // insert(
+ // vscode.Uri.parse(param.documentUri),
+ // param.range.start,
+ // response
+ // )
+ // }
+ // )
}
// showMessageAndLog("add documentation: " + s"${dyn.uri}, ${dyn.range}, ${dyn.llmResponse}")
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index d7bff12..0916ce5 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -93,6 +93,13 @@ object llmPrompt {
"You are given a text or code snippet wrapped in a tag and a TASK requirement. " +
"You are going to return the new snippet according to the TASK requirement. "
}
+
+ def generateDocs(language: String) = {
+ "generate short documentation for the input code, " +
+ "and return only the documentation for language: " + language +
+ "the documentation shall be the format according to the language, " +
+ "but don't wrap it with backticks or any other tags."
+ }
}
/* example:
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 8037a53..71884f4 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -36,36 +36,13 @@ object CodeActions {
llm.sendPrompt(
llmPrompt.Modification(
code = selectedCode, //
- taskRequirement = "add documentation"
+ taskRequirement = llmPrompt.generateDocs(documentProps.getLanguage())
)
)
- val editor = new vscode.WorkspaceEdit() {
- // linking issue from compiler
- // llmResponse.wait()
- // val response = Await.result(llmResponse, Duration.Inf)
- // // llmResponse.toJSPromise.wait()
- // showMessageAndLog("llm response: " + response)
- // insert(
- // uri = document.uri,
- // position = range.start,
- // newText = response
- // )
- }
-
- llmResponse.foreach { response =>
- showMessageAndLog("llm response: " + response)
- // does not work since it is not in the same thread?
- editor.insert(
- uri = document.uri,
- position = range.start,
- newText = response
- )
- }
-
val fix1 =
new vscode.CodeAction(
- title = "generate documentation",
+ title = "add documentation for selected code",
kind = vscode.CodeActionKind.QuickFix
) {
isPreferred = true // show it first
diff --git a/src/main/scala/vscextension/documentProps.scala b/src/main/scala/vscextension/documentProps.scala
index da86ee3..ade4f08 100644
--- a/src/main/scala/vscextension/documentProps.scala
+++ b/src/main/scala/vscextension/documentProps.scala
@@ -12,15 +12,19 @@ object documentProps {
* like the language of the document, the project root, etc.
*/
def showProps = {
- vscode.window.activeTextEditor.toOption match {
- case None =>
- showMessageAndLog("no active editor")
- case Some(editor) =>
- showMessageAndLog("current language: " + editor.document.languageId)
- }
+ showMessageAndLog("document language: " + getLanguage())
val projectRoot = vscode.workspace.rootPath.getOrElse("")
showMessageAndLog("project root: " + projectRoot)
}
+
+ def getLanguage() = {
+ vscode.window.activeTextEditor.toOption match {
+ case None =>
+ ""
+ case Some(editor) =>
+ editor.document.languageId
+ }
+ }
}
From 032c1701743aa850fa64a9dd4d894df64decf0bc Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 17 Feb 2025 17:11:01 +0100
Subject: [PATCH 16/46] Refactor command registration and improve command
handling; replace command definitions with tuples and create vscCommands
object for command registration
---
.../scala/functorcoder/actions/Commands.scala | 13 ++++---
.../scala/functorcoder/llm/llmPrompt.scala | 39 ++++++++++++-------
src/main/scala/vscextension/CodeActions.scala | 13 ++-----
.../scala/vscextension/extensionMain.scala | 2 +-
src/main/scala/vscextension/statusBar.scala | 2 +-
.../{commands.scala => vscCommands.scala} | 2 +-
6 files changed, 39 insertions(+), 32 deletions(-)
rename src/main/scala/vscextension/{commands.scala => vscCommands.scala} (97%)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 477ba73..8d78755 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -1,4 +1,5 @@
package functorcoder.actions
+
import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
@@ -8,23 +9,25 @@ import scala.scalajs.js
import vscextension.quickPick
import vscextension.facade.vscodeUtils.showMessageAndLog
+
import scala.concurrent.Future
import functorcoder.types.editorCtx.codeActionParam
-import typings.std.stdStrings.ins
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
object Commands {
type CommandT = Any => Unit
// all the commands here
- val commandMenu = "functorcoder.menu"
- val commandAddDocumentation = "functorcoder.addDocumentation"
+ val commandMenu =
+ ("functorcoder.menu", quickPick.showQuickPick)
+ val commandAddDocumentation =
+ ("functorcoder.addDocumentation", addDocumentation)
// list of all commands to be registered
val commandList: Seq[(String, CommandT)] =
Seq(
- (commandMenu, quickPick.showQuickPick),
- (commandAddDocumentation, addDocumentation)
+ commandMenu,
+ commandAddDocumentation
)
// the main menu items
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 0916ce5..992a42d 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -1,11 +1,11 @@
package functorcoder.llm
-/** large language model (LLM) AI prompt
+/** prompts for the llm
*
* for completion, code generation, etc.
*/
object llmPrompt {
- // trait will have undefined value
+ // trait will have undefined value, so we use abstract class
sealed abstract class Prompt(val assistantMsg: String) {
def generatePrompt: String
def getAssistantMessage: String = assistantMsg
@@ -34,15 +34,7 @@ object llmPrompt {
assistantMessage: String = promptText.prompt1
) extends Prompt(assistantMessage) {
def generatePrompt = {
- // shall return a string wrapped with
- // s"""
- // |${codeWithHole}
- // |
- // |""".stripMargin
-
- /* |
- |TASK: ${taskRequirement}
- */
+
codeWithHole
}
@@ -71,6 +63,25 @@ object llmPrompt {
}
}
+ /** tags, placeholders and templates used in the prompt
+ *
+ * for code completion
+ */
+ case class QueryTags(
+ hole: String, //
+ queryStart: String,
+ queryEnd: String,
+ task: String
+ )
+
+ val tags1 =
+ QueryTags(
+ hole = "{{HOLE}}", //
+ queryStart = "{{QUERY_START}}",
+ queryEnd = "{{QUERY_END}}",
+ task = "{{TASK}}"
+ )
+
/** prompts engineering
*
* more like art than science. just try different prompts and see what works best
@@ -79,18 +90,18 @@ object llmPrompt {
val hole = "{{FILL_HERE}}"
val prompt1 =
"You are a code or text autocompletion assistant. " +
- "In the provided input, missing code or text are marked as '{{FILL_HERE}}'. " +
+ s"In the provided input, missing code or text are marked as $hole. " +
"Your task is to output only the snippet that replace the placeholder, " +
"ensuring that indentation and formatting remain consistent with the context. Don't quote your output"
val prompt2 =
"You are a hole filler," +
"You are given a string with a hole: " +
- "{{FILL_HERE}} in the string, " +
+ s"$hole in the string, " +
"your task is to replace this hole with your reply." +
"you only return the string for the hole with indentation, without any quotes"
val promptTask =
- "You are given a text or code snippet wrapped in a tag and a TASK requirement. " +
+ "You are given a text or code snippet wrapped in tag and a TASK requirement. " +
"You are going to return the new snippet according to the TASK requirement. "
}
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 71884f4..a3985fc 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -11,6 +11,7 @@ import cats.syntax.show
import scala.concurrent.duration.Duration
import scala.concurrent.Await
import scala.concurrent.Future
+import functorcoder.types.editorCtx.*
/** Code actions are commands provided at the cursor in the editor, so users can
*
@@ -46,33 +47,25 @@ object CodeActions {
kind = vscode.CodeActionKind.QuickFix
) {
isPreferred = true // show it first
- // should invoke a command to perform the action
- import functorcoder.types.editorCtx.*
val args: codeActionParam[Future[String]] = new codeActionParam(
document.uri.toString(),
range,
llmResponse
)
-
+ // invoke command
command = vscode
.Command(
- command = functorcoder.actions.Commands.commandAddDocumentation, //
+ command = functorcoder.actions.Commands.commandAddDocumentation._1, //
title = "add documentation" //
)
.setArguments(js.Array(args))
- // edit = editor
- // optional command to run when the code action is selected
- // command = ..
}
// can return array or promise of array
js.Array(fix1)
-
- // the code action for learn more
}
- // override def provideCodeActions
def provideCodeActions(
document: vscode.TextDocument,
range: vscode.Selection,
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index bb67f0b..a162188 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -21,7 +21,7 @@ object extensionMain {
showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
- commands.registerAllCommands(context)
+ vscCommands.registerAllCommands(context)
// show the status bar
val statusBarItem =
diff --git a/src/main/scala/vscextension/statusBar.scala b/src/main/scala/vscextension/statusBar.scala
index e88da94..d8c7a7b 100644
--- a/src/main/scala/vscextension/statusBar.scala
+++ b/src/main/scala/vscextension/statusBar.scala
@@ -13,7 +13,7 @@ object statusBar {
val name = "functor"
statusBarItem.text = name
statusBarItem.name = name
- statusBarItem.command = Commands.commandMenu
+ statusBarItem.command = Commands.commandMenu._1
statusBarItem.show()
context.pushDisposable(statusBarItem.asInstanceOf[vscode.Disposable])
diff --git a/src/main/scala/vscextension/commands.scala b/src/main/scala/vscextension/vscCommands.scala
similarity index 97%
rename from src/main/scala/vscextension/commands.scala
rename to src/main/scala/vscextension/vscCommands.scala
index 1b7cd35..9dce0aa 100644
--- a/src/main/scala/vscextension/commands.scala
+++ b/src/main/scala/vscextension/vscCommands.scala
@@ -11,7 +11,7 @@ import facade.vscodeUtils.*
*
* This object registers all the commands in the extension.
*/
-object commands {
+object vscCommands {
/** Register all the commands in the extension.
*
From 283dde47bea099b85e64f1fc6dbf085700723baf Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 17 Feb 2025 17:11:20 +0100
Subject: [PATCH 17/46] Remove unused imports and streamline code in LLM and
VSCode extension files
---
src/main/scala/functorcoder/llm/llmMain.scala | 1 -
src/main/scala/vscextension/CodeActions.scala | 5 -----
src/main/scala/vscextension/extensionMain.scala | 3 +--
src/main/scala/vscextension/vscCommands.scala | 1 -
4 files changed, 1 insertion(+), 9 deletions(-)
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index d10bbba..5f24ff2 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -12,7 +12,6 @@ import scala.scalajs.js.Thenable.Implicits.*
import scala.concurrent.Future
import functorcoder.editorUI.editorConfig
-import cats.syntax.show
/** large language model (LLM) AI main
*
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index a3985fc..465944f 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -1,15 +1,10 @@
package vscextension
import typings.vscode.mod as vscode
import scala.scalajs.js
-import scala.concurrent.ExecutionContext.Implicits.global
-import scala.scalajs.js.JSConverters.JSRichFutureNonThenable
import facade.vscodeUtils.*
import functorcoder.llm.llmMain.llmAgent
import functorcoder.llm.llmPrompt
-import cats.syntax.show
-import scala.concurrent.duration.Duration
-import scala.concurrent.Await
import scala.concurrent.Future
import functorcoder.types.editorCtx.*
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index a162188..b137d90 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -24,8 +24,7 @@ object extensionMain {
vscCommands.registerAllCommands(context)
// show the status bar
- val statusBarItem =
- statusBar.createStatusBarItem(context)
+ statusBar.createStatusBarItem(context)
// statusBarItem.text = "functorcoder ok"
// show the current language of the document
documentProps.showProps
diff --git a/src/main/scala/vscextension/vscCommands.scala b/src/main/scala/vscextension/vscCommands.scala
index 9dce0aa..bafe9d5 100644
--- a/src/main/scala/vscextension/vscCommands.scala
+++ b/src/main/scala/vscextension/vscCommands.scala
@@ -2,7 +2,6 @@ package vscextension
import typings.vscode.mod as vscode
-import scala.collection.immutable
import scala.scalajs.js
import facade.vscodeUtils.*
From 486f4b1947bc4d224e3aaf043b0a63790b64900f Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 18 Feb 2025 15:31:54 +0100
Subject: [PATCH 18/46] Enhance VSCode extension configuration by adding
maxTokens parameter; refactor command handling and improve code structure for
better maintainability
---
.../scala/functorcoder/actions/Commands.scala | 22 +++++-----------
.../functorcoder/editorUI/editorConfig.scala | 2 +-
src/main/scala/functorcoder/llm/llmMain.scala | 15 +++++------
.../scala/functorcoder/llm/openaiReq.scala | 12 ++++-----
.../scala/functorcoder/llm/wk.worksheet.sc | 8 ++++++
src/main/scala/vscextension/CodeActions.scala | 15 +++++------
.../facade/CodeActionProvider.scala | 26 +++++++++++++++++++
src/main/scala/vscextension/settings.scala | 3 ++-
8 files changed, 62 insertions(+), 41 deletions(-)
create mode 100644 src/main/scala/vscextension/facade/CodeActionProvider.scala
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 8d78755..c1952f2 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -1,22 +1,21 @@
package functorcoder.actions
import scala.concurrent.ExecutionContext.Implicits.global
-
-import typings.vscode.mod as vscode
-
import scala.collection.immutable
import scala.scalajs.js
+import scala.concurrent.Future
+
+import typings.vscode.mod as vscode
import vscextension.quickPick
import vscextension.facade.vscodeUtils.showMessageAndLog
-import scala.concurrent.Future
import functorcoder.types.editorCtx.codeActionParam
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
object Commands {
- type CommandT = Any => Unit
+ type CommandT = Any => Any
// all the commands here
val commandMenu =
("functorcoder.menu", quickPick.showQuickPick)
@@ -48,20 +47,11 @@ object Commands {
showMessageAndLog("no active editor!")
case Some(ed) =>
ed.insertSnippet(
- new vscode.SnippetString("\n" + response), //
- param.range.start
+ new vscode.SnippetString(response + "\n"), //
+ param.range.start // insert at the start of the selection
)
}
- // vscode.workspace.applyEdit(
- // new vscode.WorkspaceEdit {
- // insert(
- // vscode.Uri.parse(param.documentUri),
- // param.range.start,
- // response
- // )
- // }
- // )
}
// showMessageAndLog("add documentation: " + s"${dyn.uri}, ${dyn.range}, ${dyn.llmResponse}")
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index 561b193..00505e9 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -2,6 +2,6 @@ package functorcoder.editorUI
// https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
object editorConfig {
- case class Config(openaiApiKey: String, openaiUrl: String)
+ case class Config(openaiApiKey: String, openaiUrl: String, maxTokens: Int)
}
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index 5f24ff2..726e535 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -27,9 +27,9 @@ object llmMain {
* completion prompt object
* @return
*/
- def prompt2str(inputPrompt: llmPrompt.Prompt) = {
- showMessageAndLog(s"prompt: ${inputPrompt}")
- showMessageAndLog(s"prompt assistant: ${inputPrompt.getAssistantMessage}")
+ def prompt2str(editorCfg: editorConfig.Config, inputPrompt: llmPrompt.Prompt) = {
+ // showMessageAndLog(s"prompt: ${inputPrompt}")
+ // showMessageAndLog(s"prompt assistant: ${inputPrompt.getAssistantMessage}")
val openAiRequest = openaiReq
.OpenAiRequest(
@@ -37,10 +37,11 @@ object llmMain {
openaiReq.Message(roles.user, inputPrompt.generatePrompt),
openaiReq.Message(roles.system, inputPrompt.getAssistantMessage)
),
- openaiReq.models.gpt4o
+ openaiReq.models.gpt4oMini,
+ max_tokens = Some(editorCfg.maxTokens)
)
- showMessageAndLog(s"openai request: ${openAiRequest}")
+ // showMessageAndLog(s"openai request: ${openAiRequest}")
openAiRequest.toJson
}
@@ -58,9 +59,7 @@ object llmMain {
*/
def sendPrompt(input: llmPrompt.Prompt) = {
- val requestStr = prompt2str(
- input
- )
+ val requestStr = prompt2str(editorCfg, input)
val requestOptions = getRequestOptions(requestStr)
diff --git a/src/main/scala/functorcoder/llm/openaiReq.scala b/src/main/scala/functorcoder/llm/openaiReq.scala
index 9a2ea85..a31fe7f 100644
--- a/src/main/scala/functorcoder/llm/openaiReq.scala
+++ b/src/main/scala/functorcoder/llm/openaiReq.scala
@@ -108,12 +108,12 @@ object openaiReq {
*/
case class OpenAiRequest(
messages: Seq[Message],
- model: String
- // frequency_penalty: Option[Double] = None,
- // logit_bias: Option[Map[String, Int]] = None,
- // logprobs: Option[Boolean] = None,
- // top_logprobs: Option[Int] = None,
- // max_tokens: Option[Int] = None,
+ model: String,
+ frequency_penalty: Option[Double] = None,
+ logit_bias: Option[Map[String, Int]] = None,
+ logprobs: Option[Boolean] = None,
+ top_logprobs: Option[Int] = None,
+ max_tokens: Option[Int] = None
// n: Option[Int] = None,
// presence_penalty: Option[Double] = None,
// response_format: Option[String] = None,
diff --git a/src/main/scala/functorcoder/llm/wk.worksheet.sc b/src/main/scala/functorcoder/llm/wk.worksheet.sc
index 6bccc7e..3cd79de 100644
--- a/src/main/scala/functorcoder/llm/wk.worksheet.sc
+++ b/src/main/scala/functorcoder/llm/wk.worksheet.sc
@@ -18,3 +18,11 @@ def m1(t1: trait1) = {
println(s"t1 param1: ${t1.param1}")
}
// c1.method1
+"""Complex systems, Chaos and ecosystem part 2: mathematical foundation of dynamic system
+
+
+Previously, I have introduced the basic concepts and examples of nonlinear dynamics and chaos, as well as the current reductionist philosophy in contrast to the dynamical systems approach in "Complex systems, Chaos and ecosystem part 1: the defiance to reductionism", now it's time to dive into dynamical systems, the mathematical foundation of complex systems.
+
+# dynamical systems
+Dynamical systems are a branch of mathematics focused on the study
+""".length()
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 465944f..276305e 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -1,11 +1,13 @@
package vscextension
-import typings.vscode.mod as vscode
+
import scala.scalajs.js
+import scala.concurrent.Future
+
+import typings.vscode.mod as vscode
import facade.vscodeUtils.*
import functorcoder.llm.llmMain.llmAgent
import functorcoder.llm.llmPrompt
-import scala.concurrent.Future
import functorcoder.types.editorCtx.*
/** Code actions are commands provided at the cursor in the editor, so users can
@@ -69,11 +71,11 @@ object CodeActions {
): vscode.ProviderResult[js.Array[vscode.CodeAction]] = {
// check who triggers the code action, since vscode may trigger it automatically
- val res = context.triggerKind match {
+ context.triggerKind match {
case vscode.CodeActionTriggerKind.Invoke =>
// triggered by user
- showMessageAndLog("selected code: " + document.getText(range))
+ // showMessageAndLog("selected code: " + document.getText(range))
createCodeAction(document, range, context)
case _ =>
@@ -81,11 +83,7 @@ object CodeActions {
js.Array()
}
-
- res.asInstanceOf[vscode.ProviderResult[js.Array[vscode.CodeAction]]]
-
}
- // cast the object to the required type
}.asInstanceOf[vscode.CodeActionProvider[vscode.CodeAction]]
val registration: vscode.Disposable =
@@ -100,7 +98,6 @@ object CodeActions {
)
context.pushDisposable(registration)
- showMessageAndLog("registered code actions")
}
}
diff --git a/src/main/scala/vscextension/facade/CodeActionProvider.scala b/src/main/scala/vscextension/facade/CodeActionProvider.scala
new file mode 100644
index 0000000..9da405a
--- /dev/null
+++ b/src/main/scala/vscextension/facade/CodeActionProvider.scala
@@ -0,0 +1,26 @@
+package vscextension
+import typings.vscode.mod as vscode
+import scala.scalajs.js
+
+import facade.vscodeUtils.*
+import functorcoder.llm.llmMain.llmAgent
+import functorcoder.llm.llmPrompt
+import scala.concurrent.Future
+import functorcoder.types.editorCtx.*
+
+/** Code actions are commands provided at the cursor in the editor, so users can
+ *
+ * quickly fix issues or refactor code, etc.
+ *
+ * https://github.com/microsoft/vscode-extension-samples/blob/main/code-actions-sample/README.md
+ *
+ * manually created facade due to scalablytyped issue
+ */
+trait CodeActionProvider {
+ def provideCodeActions(
+ document: vscode.TextDocument,
+ range: vscode.Selection,
+ context: vscode.CodeActionContext,
+ token: vscode.CancellationToken
+ ): vscode.ProviderResult[js.Array[vscode.CodeAction]]
+}
diff --git a/src/main/scala/vscextension/settings.scala b/src/main/scala/vscextension/settings.scala
index 8350f6d..cc97426 100644
--- a/src/main/scala/vscextension/settings.scala
+++ b/src/main/scala/vscextension/settings.scala
@@ -17,7 +17,8 @@ object settings {
val openaiUrl =
config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
- Config(openaiApiKey, openaiUrl)
+ val maxTokens = config.get[Int]("maxTokens").getOrElse(1000)
+ Config(openaiApiKey, openaiUrl, maxTokens)
}
extension (config: vscode.WorkspaceConfiguration) {
From 025beb4ac44699d996edcf2948f770d9b9939b0b Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 19 Feb 2025 17:04:00 +0100
Subject: [PATCH 19/46] Refactor build configuration and remove
CodeActionProvider; add InlineEdit functionality for enhanced user
interaction
---
build.sbt | 11 +++-
.../facade/CodeActionProvider.scala | 26 ---------
.../vscextension/facade/InlineEdit.scala | 55 +++++++++++++++++++
3 files changed, 63 insertions(+), 29 deletions(-)
delete mode 100644 src/main/scala/vscextension/facade/CodeActionProvider.scala
create mode 100644 src/main/scala/vscextension/facade/InlineEdit.scala
diff --git a/build.sbt b/build.sbt
index 355b570..5f1aca8 100644
--- a/build.sbt
+++ b/build.sbt
@@ -37,10 +37,15 @@ lazy val root = project
),
Compile / npmDependencies ++=
Seq(
- "@types/vscode" -> "1.96.0", //
+ // vscode dependencies
+ "@types/vscode" -> "1.96.0",
+ // "@vscode/dts" -> "0.4.1", // it's just a utility to download sources
+ "vscode-languageclient" -> "9.0.1", // working with manuallly created facade
+
+ // other dependencies
"@types/node" -> "16.11.7", // ts 3.7
- "@types/node-fetch" -> "2.5.12", // ts 3.7,compile error for scalablytyped
- "vscode-languageclient" -> "9.0.1" // working with manuallly created facade
+ "@types/node-fetch" -> "2.5.12" // ts 3.7,compile error for scalablytyped
+
),
/* ++ // check if it is running in test
(if (sys.props.get("testing") != Some("true"))
diff --git a/src/main/scala/vscextension/facade/CodeActionProvider.scala b/src/main/scala/vscextension/facade/CodeActionProvider.scala
deleted file mode 100644
index 9da405a..0000000
--- a/src/main/scala/vscextension/facade/CodeActionProvider.scala
+++ /dev/null
@@ -1,26 +0,0 @@
-package vscextension
-import typings.vscode.mod as vscode
-import scala.scalajs.js
-
-import facade.vscodeUtils.*
-import functorcoder.llm.llmMain.llmAgent
-import functorcoder.llm.llmPrompt
-import scala.concurrent.Future
-import functorcoder.types.editorCtx.*
-
-/** Code actions are commands provided at the cursor in the editor, so users can
- *
- * quickly fix issues or refactor code, etc.
- *
- * https://github.com/microsoft/vscode-extension-samples/blob/main/code-actions-sample/README.md
- *
- * manually created facade due to scalablytyped issue
- */
-trait CodeActionProvider {
- def provideCodeActions(
- document: vscode.TextDocument,
- range: vscode.Selection,
- context: vscode.CodeActionContext,
- token: vscode.CancellationToken
- ): vscode.ProviderResult[js.Array[vscode.CodeAction]]
-}
diff --git a/src/main/scala/vscextension/facade/InlineEdit.scala b/src/main/scala/vscextension/facade/InlineEdit.scala
new file mode 100644
index 0000000..5dd64c7
--- /dev/null
+++ b/src/main/scala/vscextension/facade/InlineEdit.scala
@@ -0,0 +1,55 @@
+package vscextension.facade
+
+import scala.scalajs.js.annotation.JSImport
+import scala.scalajs.js
+
+import typings.vscode.mod as vscode
+import typings.vscode.mod.Command
+import scala.scalajs.js.Promise
+
+/** a dialog in the editor that users can accept or reject
+ *
+ * part of the
+ *
+ * https://github.com/microsoft/vscode/blob/main/src/vscode-dts/vscode.proposed.inlineEdit.d.ts
+ */
+
+object InlineEdit {
+
+ @js.native
+ @JSImport("vscode", "InlineEdit")
+ class InlineEdit extends js.Object {
+ def this(text: String, range: vscode.Selection) = this()
+ val text: String = js.native
+ val range: vscode.Selection = js.native
+
+ val showRange: Range = js.native
+ val accepted: Command = js.native
+ val rejected: Command = js.native
+ val shown: Command = js.native
+ val commands: Command = js.native
+ val action: Command = js.native
+ }
+
+ @js.native
+ trait InlineEditContext extends js.Object {
+ val triggerKind: vscode.CodeActionTriggerKind = js.native
+ }
+
+// @js.native
+ trait InlineEditProvider extends js.Object {
+ def provideInlineEdits(
+ document: vscode.TextDocument,
+ content: InlineEditContext,
+ token: vscode.CancellationToken
+ ): js.Promise[js.Array[InlineEdit]]
+ }
+
+ @JSImport("vscode", "languages")
+ @js.native
+ object languages extends js.Object {
+ def registerInlineEditProvider(selector: vscode.DocumentSelector, provider: InlineEditProvider): vscode.Disposable =
+ js.native
+ }
+
+}
From 1c9494b4e3c71ebe70dcb85535b173a5c33c86ae Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 3 Mar 2025 23:03:11 +0100
Subject: [PATCH 20/46] Add file and folder creation functionality; implement
tree parsing and enhance prompt handling
---
build.sbt | 8 ++
.../scala/functorcoder/actions/Refactor.scala | 0
.../functorcoder/actions/createFiles.scala | 49 ++++++++++
.../scala/functorcoder/algo/treeParse.scala | 97 +++++++++++++++++++
.../scala/functorcoder/llm/llmPrompt.scala | 61 ++++++++----
.../scala/functorcoder/llm/wk.worksheet.sc | 14 +--
6 files changed, 202 insertions(+), 27 deletions(-)
delete mode 100644 src/main/scala/functorcoder/actions/Refactor.scala
create mode 100644 src/main/scala/functorcoder/actions/createFiles.scala
create mode 100644 src/main/scala/functorcoder/algo/treeParse.scala
diff --git a/build.sbt b/build.sbt
index 5f1aca8..5458d23 100644
--- a/build.sbt
+++ b/build.sbt
@@ -29,10 +29,18 @@ lazy val root = project
// testOptions += Tests.Setup(_ => sys.props("testing") = "true"),
Compile / fastOptJS / artifactPath := baseDirectory.value / "out" / "extension.js",
Compile / fullOptJS / artifactPath := baseDirectory.value / "out" / "extension.js",
+ resolvers ++= Seq(
+ Resolver.jcenterRepo,
+ "jitpack" at "https://jitpack.io",
+ "Sonatype OSS Snapshots" at "https://oss.sonatype.org/content/repositories/snapshots"
+ ),
libraryDependencies ++= Seq(
// "com.lihaoyi" %%% "utest" % "0.8.2" % "test",
// ("org.latestbit", "circe-tagged-adt-codec", "0.11.0")
"org.latestbit" %%% "circe-tagged-adt-codec" % "0.11.0",
+ "com.github.doofin.stdScala" %%% "stdscala" % "387b33df3a",
+
+ // test dependencies
"org.scalameta" %%% "munit" % "0.7.29" % Test
),
Compile / npmDependencies ++=
diff --git a/src/main/scala/functorcoder/actions/Refactor.scala b/src/main/scala/functorcoder/actions/Refactor.scala
deleted file mode 100644
index e69de29..0000000
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
new file mode 100644
index 0000000..2d836db
--- /dev/null
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -0,0 +1,49 @@
+package functorcoder.actions
+
+import scala.collection.mutable.ArrayBuffer
+import com.doofin.stdScala.dataTypes.Tree.TreeNode
+import functorcoder.algo.treeParse
+import vscextension.facade.vscodeUtils.showMessageAndLog
+
+/** create files and folders according to the prompt
+ */
+object createFiles {
+
+ /** parse the prompt response to tree of files and folders
+ *
+ * The prompt response is like: (root [(folder1 [(file1 file2) folder2]) folder3])
+ *
+ * assumes the prompt response is one of tree representation
+ *
+ * @param promptResponse
+ * the response from the prompt
+ */
+ def parseFilesTree(promptResponse: String, retry: Int = 3): Unit = {
+ println("Creating files and folders")
+
+ treeParse.parse(promptResponse) match {
+ case scala.util.Success(tree) =>
+ createFilesAndFolders(tree)
+ case scala.util.Failure(exception) =>
+ showMessageAndLog(s"Trying again with $retry retries left")
+ if (retry > 0) {
+ println(s"Retrying with retry=$retry")
+ parseFilesTree(promptResponse, retry - 1)
+ }
+ }
+ }
+
+ /** create files and folders according to the tree
+ *
+ * @param tree
+ * the tree of files and folders
+ */
+ def createFilesAndFolders(tree: TreeNode[String]): Unit = {
+ // recursively create files and folders
+ showMessageAndLog(s"Files and folders tree: $tree")
+ val TreeNode(root, children) = tree
+ children.foreach { child =>
+ createFilesAndFolders(child)
+ }
+ }
+}
diff --git a/src/main/scala/functorcoder/algo/treeParse.scala b/src/main/scala/functorcoder/algo/treeParse.scala
new file mode 100644
index 0000000..bc0ded7
--- /dev/null
+++ b/src/main/scala/functorcoder/algo/treeParse.scala
@@ -0,0 +1,97 @@
+package functorcoder.algo
+
+import scala.collection.mutable.ArrayBuffer
+import com.doofin.stdScala.dataTypes.Tree.TreeNode
+import scala.util.Try
+
+object treeParse {
+ val exampleInput = "(root [(folder1 [(file1 file2) folder2]) folder3])"
+ val exampleSyntax = "tree := (string [tree tree ...])"
+
+ def parse(input: String): Try[TreeNode[String]] = Try {
+ val tokens = tokenize(input)
+ val (node, remaining) = parseNode(tokens)
+ if (remaining.nonEmpty)
+ println(s"Unconsumed tokens: $remaining")
+ node
+ }
+
+ private def tokenize(input: String): List[String] = {
+ val tokenPattern = """(\(|\)|\[|\]|[^\s\(\)\[\]]+)""".r
+ tokenPattern.findAllIn(input).toList
+ }
+
+ // Parse a node. A node is expected to start with a "(",
+ // followed by a value token and then either a bracketed children list
+ // or inline children (if any), and finally a ")".
+ private def parseNode(tokens: List[String]): (TreeNode[String], List[String]) = tokens match {
+ case "(" :: rest =>
+ rest match {
+ case value :: afterValue =>
+ // If the next token is "[", we parse a bracketed children list.
+ if (afterValue.nonEmpty && afterValue.head == "[") {
+ val (children, afterBracket) = parseChildrenUntil(afterValue.tail, "]")
+ afterBracket match {
+ case ")" :: tail => (TreeNode(value, children), tail)
+ case _ => throw new RuntimeException("Expected ) after children list")
+ }
+ } else {
+ // Otherwise, if the next token is not ")", then we assume inline children.
+ if (afterValue.nonEmpty && afterValue.head == ")") {
+ // No children case.
+ (TreeNode(value), afterValue.tail)
+ } else {
+ val (children, afterInline) = parseChildrenUntilInline(afterValue)
+ (TreeNode(value, children), afterInline)
+ }
+ }
+ case Nil =>
+ throw new RuntimeException("Expected node value after (")
+ }
+ // When not starting with "(", treat the token as a leaf.
+ case token :: rest =>
+ (TreeNode(token), rest)
+ case Nil =>
+ throw new RuntimeException("Unexpected end of tokens")
+ }
+
+ // Helper: parse children until we reach the given terminator ("]" for bracketed lists).
+ // Returns the children (as TreeNode[String]) and the remaining tokens (after dropping the terminator).
+ private def parseChildrenUntil(
+ tokens: List[String],
+ terminator: String
+ ): (ArrayBuffer[TreeNode[String]], List[String]) = {
+ val children = ArrayBuffer[TreeNode[String]]()
+ var rem = tokens
+ while (rem.nonEmpty && rem.head != terminator) {
+ if (rem.head == "(") {
+ val (child, newRem) = parseNode(rem)
+ children += child
+ rem = newRem
+ } else {
+ // A plain token becomes a leaf node.
+ children += TreeNode(rem.head)
+ rem = rem.tail
+ }
+ }
+ if (rem.isEmpty) throw new RuntimeException(s"Expected terminator $terminator")
+ (children, rem.tail) // drop the terminator
+ }
+
+ private def parseChildrenUntilInline(tokens: List[String]): (ArrayBuffer[TreeNode[String]], List[String]) = {
+ val children = ArrayBuffer[TreeNode[String]]()
+ var rem = tokens
+ while (rem.nonEmpty && rem.head != ")") {
+ if (rem.head == "(") {
+ val (child, newRem) = parseNode(rem)
+ children += child
+ rem = newRem
+ } else {
+ children += TreeNode(rem.head)
+ rem = rem.tail
+ }
+ }
+ if (rem.isEmpty) throw new RuntimeException("Expected ) at end of inline children list")
+ (children, rem.tail) // drop the closing ")"
+ }
+}
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 992a42d..94d0424 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -5,6 +5,26 @@ package functorcoder.llm
* for completion, code generation, etc.
*/
object llmPrompt {
+
+ /** tags, placeholders and templates used in the prompt
+ *
+ * for code completion
+ */
+ case class QueryTags(
+ hole: String, //
+ queryStart: String,
+ queryEnd: String,
+ task: String
+ )
+
+ val tagsInUse =
+ QueryTags(
+ hole = "{{HOLE}}", //
+ queryStart = "{{QUERY_START}}",
+ queryEnd = "{{QUERY_END}}",
+ task = "{{TASK}}"
+ )
+
// trait will have undefined value, so we use abstract class
sealed abstract class Prompt(val assistantMsg: String) {
def generatePrompt: String
@@ -52,7 +72,9 @@ object llmPrompt {
case class Modification(
code: String,
taskRequirement: String,
- assistantMessage: String = promptText.promptTask
+ assistantMessage: String =
+ "You are given a text or code snippet wrapped in tag and a TASK requirement. " +
+ "You are going to return the new snippet according to the TASK requirement. "
) extends Prompt(assistantMessage) {
def generatePrompt = {
s"""
@@ -62,25 +84,25 @@ object llmPrompt {
|""".stripMargin
}
}
+ case class CreateFiles(
+ userRequest: String,
+ assistantMessage: String =
+ s"You are given a user requirement wrapped in ${tagsInUse.queryStart} and ${tagsInUse.queryEnd}, and a TASK requirement at ${tagsInUse.task}. " +
+ "You are going to return the code snippet according to the TASK requirement. "
+ ) extends Prompt(assistantMessage) {
+ def generatePrompt = {
+ import functorcoder.algo.treeParse
- /** tags, placeholders and templates used in the prompt
- *
- * for code completion
- */
- case class QueryTags(
- hole: String, //
- queryStart: String,
- queryEnd: String,
- task: String
- )
+ val task =
+ s"parse the prompt response to tree of files and folders in the format: ${treeParse.exampleSyntax}. An example input is: ${treeParse.exampleInput}"
- val tags1 =
- QueryTags(
- hole = "{{HOLE}}", //
- queryStart = "{{QUERY_START}}",
- queryEnd = "{{QUERY_END}}",
- task = "{{TASK}}"
- )
+ s"""${tagsInUse.queryStart}
+ |${userRequest}
+ |${tagsInUse.queryEnd}
+ |${tagsInUse.task} : ${task}
+ |""".stripMargin
+ }
+ }
/** prompts engineering
*
@@ -100,9 +122,6 @@ object llmPrompt {
"your task is to replace this hole with your reply." +
"you only return the string for the hole with indentation, without any quotes"
- val promptTask =
- "You are given a text or code snippet wrapped in tag and a TASK requirement. " +
- "You are going to return the new snippet according to the TASK requirement. "
}
def generateDocs(language: String) = {
diff --git a/src/main/scala/functorcoder/llm/wk.worksheet.sc b/src/main/scala/functorcoder/llm/wk.worksheet.sc
index 3cd79de..8a1e237 100644
--- a/src/main/scala/functorcoder/llm/wk.worksheet.sc
+++ b/src/main/scala/functorcoder/llm/wk.worksheet.sc
@@ -1,6 +1,8 @@
import functorcoder.llm.llmPrompt
import functorcoder.llm.llmPrompt.Prompt
-import fansi.Str
+import scala.collection.mutable.ArrayBuffer
+import functorcoder.algo.treeParse
+
val Modification = llmPrompt
.Modification(code = "val x = 1", taskRequirement = "add documentation")
@@ -18,11 +20,11 @@ def m1(t1: trait1) = {
println(s"t1 param1: ${t1.param1}")
}
// c1.method1
-"""Complex systems, Chaos and ecosystem part 2: mathematical foundation of dynamic system
+import io.circe.generic.auto._
+import com.doofin.stdScalaCross.TreeNode
-Previously, I have introduced the basic concepts and examples of nonlinear dynamics and chaos, as well as the current reductionist philosophy in contrast to the dynamical systems approach in "Complex systems, Chaos and ecosystem part 1: the defiance to reductionism", now it's time to dive into dynamical systems, the mathematical foundation of complex systems.
+TreeNode("root", ArrayBuffer())
-# dynamical systems
-Dynamical systems are a branch of mathematics focused on the study
-""".length()
+val input = "(root [(folder1 [(file1 file2) folder2]) folder3])"
+val tree = treeParse.parse(input)
From 8209e38d8ac08e2cc5609122c724e359b7de59d9 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 4 Mar 2025 15:07:02 +0100
Subject: [PATCH 21/46] Refactor command registration to include createFiles
command; update build command alias and enhance quick pick functionality for
file generation
---
build.sbt | 2 +-
package.json | 8 +++-
.../scala/functorcoder/actions/Commands.scala | 44 +++++++++++++++++--
.../scala/functorcoder/llm/llmPrompt.scala | 4 +-
.../scala/vscextension/extensionMain.scala | 2 +-
src/main/scala/vscextension/quickPick.scala | 41 +++++++++++++++++
src/main/scala/vscextension/vscCommands.scala | 5 ++-
7 files changed, 95 insertions(+), 11 deletions(-)
diff --git a/build.sbt b/build.sbt
index 5458d23..e3b8a56 100644
--- a/build.sbt
+++ b/build.sbt
@@ -71,7 +71,7 @@ lazy val root = project
)
addCommandAlias("compile", ";fastOptJS")
-addCommandAlias("dev", "~fastOptJS")
+addCommandAlias("dev", "~buildDebug")
addCommandAlias("fix", ";scalafixEnable;scalafixAll;")
// open, buildDebug are other commands added
diff --git a/package.json b/package.json
index 1742564..33fdfe9 100644
--- a/package.json
+++ b/package.json
@@ -42,8 +42,12 @@
},
"commands": [
{
- "command": "extension.helloWorld",
- "title": "Hello World"
+ "command": "functorcoder.menu",
+ "title": "functorcoder main menu"
+ },
+ {
+ "command": "functorcoder.createFiles",
+ "title": "generate files and folders"
}
],
"menus": {
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index c1952f2..909fab4 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -11,6 +11,8 @@ import vscextension.quickPick
import vscextension.facade.vscodeUtils.showMessageAndLog
import functorcoder.types.editorCtx.codeActionParam
+import functorcoder.llm.llmMain.llmAgent
+import functorcoder.algo.treeParse
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
@@ -22,11 +24,15 @@ object Commands {
val commandAddDocumentation =
("functorcoder.addDocumentation", addDocumentation)
+ val commandCreateFiles =
+ ("functorcoder.createFiles", createFilesCmd)
+
// list of all commands to be registered
- val commandList: Seq[(String, CommandT)] =
+ def commandList(llm: llmAgent): Seq[(String, CommandT)] =
Seq(
commandMenu,
- commandAddDocumentation
+ commandAddDocumentation,
+ (commandCreateFiles._1, commandCreateFiles._2(llm))
)
// the main menu items
@@ -54,6 +60,38 @@ object Commands {
}
- // showMessageAndLog("add documentation: " + s"${dyn.uri}, ${dyn.range}, ${dyn.llmResponse}")
+ }
+
+ def createFilesCmd(llm: llmAgent)(arg: Any) = {
+ val inputBoxOptions =
+ vscode
+ .InputBoxOptions()
+ .setTitle("generate files and folders")
+ .setPlaceHolder("type your description here")
+
+ for {
+ input <- vscode.window.showInputBox(inputBoxOptions).toFuture
+ response <- llm.sendPrompt(
+ functorcoder.llm.llmPrompt.CreateFiles(input match {
+ case _: Unit => "empty input!"
+ case s: String => s
+ })
+ )
+ } yield {
+ showMessageAndLog("create files: " + s"${response}")
+ val tree = treeParse.parse(response)
+ quickPick.createQuickPick(
+ title = "Files and Folders",
+ items = Seq(
+ (
+ "files created!",
+ tree.toString,
+ { () =>
+ showMessageAndLog("files created!")
+ }
+ )
+ )
+ )
+ }
}
}
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 94d0424..39a5458 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -87,14 +87,14 @@ object llmPrompt {
case class CreateFiles(
userRequest: String,
assistantMessage: String =
- s"You are given a user requirement wrapped in ${tagsInUse.queryStart} and ${tagsInUse.queryEnd}, and a TASK requirement at ${tagsInUse.task}. " +
+ s"You are given a user requirement wrapped in ${tagsInUse.queryStart} and ${tagsInUse.queryEnd}, and a TASK requirement ${tagsInUse.task}. " +
"You are going to return the code snippet according to the TASK requirement. "
) extends Prompt(assistantMessage) {
def generatePrompt = {
import functorcoder.algo.treeParse
val task =
- s"parse the prompt response to tree of files and folders in the format: ${treeParse.exampleSyntax}. An example input is: ${treeParse.exampleInput}"
+ s"parse the prompt response to tree of files and folders in the format: ${treeParse.exampleSyntax}. An example input is: ${treeParse.exampleInput}. return the tree data structure in that format."
s"""${tagsInUse.queryStart}
|${userRequest}
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index b137d90..9cb992b 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -21,7 +21,7 @@ object extensionMain {
showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
- vscCommands.registerAllCommands(context)
+ vscCommands.registerAllCommands(context, llm)
// show the status bar
statusBar.createStatusBarItem(context)
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 176097a..3374a13 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -15,6 +15,46 @@ import functorcoder.actions.Commands
*/
object quickPick {
+ def createQuickPick(
+ title: String, //
+ items: Seq[(String, String, () => Unit)],
+ modifies: vscode.QuickPick[vscode.QuickPickItem] => Unit = { _ => }
+ ) = {
+
+ val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
+ vscode.window.createQuickPick()
+
+ quickPick.title = title
+ // to customize the quick pick
+ modifies(quickPick)
+ quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
+
+ quickPick.items = items.toJSArray.map { (itemStr, itemDesc, _) => //
+ vscode
+ .QuickPickItem(itemStr)
+ .setAlwaysShow(true)
+ .setButtons(js.Array(vscode.QuickInputButtons.Back))
+ .setDescription(itemStr + " description")
+ .setDetail(itemDesc + " detail")
+ }
+
+ quickPick.onDidChangeSelection { selection =>
+ println(s"selected: ${selection(0).label}")
+ // execute the function associated with the selected item
+ val selected = items.find(_._1 == selection(0).label)
+ selected.foreach { (_, _, fun) =>
+ fun()
+ quickPick.hide()
+ }
+ }
+
+ quickPick.onDidHide({ _ =>
+ quickPick.hide()
+ })
+
+ quickPick.show()
+ }
+
def showQuickPick(arg: Any): Unit = {
val items =
Commands.mainMenuItems.map(_._1).toJSArray
@@ -62,4 +102,5 @@ object quickPick {
quickPick.show()
}
+
}
diff --git a/src/main/scala/vscextension/vscCommands.scala b/src/main/scala/vscextension/vscCommands.scala
index bafe9d5..6ce49d4 100644
--- a/src/main/scala/vscextension/vscCommands.scala
+++ b/src/main/scala/vscextension/vscCommands.scala
@@ -5,6 +5,7 @@ import typings.vscode.mod as vscode
import scala.scalajs.js
import facade.vscodeUtils.*
+import functorcoder.llm.llmMain.llmAgent
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*
@@ -17,10 +18,10 @@ object vscCommands {
* @param context
* the vscode extension context
*/
- def registerAllCommands(context: vscode.ExtensionContext) = {
+ def registerAllCommands(context: vscode.ExtensionContext, llm: llmAgent) = {
val allCommands =
- functorcoder.actions.Commands.commandList
+ functorcoder.actions.Commands.commandList(llm)
// register the commands
allCommands foreach { (name, fun) =>
context.pushDisposable(
From 52105b8333e7f3e6119d5f9b66abfdbf5382196a Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 30 Mar 2025 00:11:44 +0100
Subject: [PATCH 22/46] Enhance status bar functionality by adding spinning
status indicators during loading; update README for clarity
---
README.md | 2 +-
.../scala/functorcoder/actions/Commands.scala | 4 ++++
.../vscextension/inlineCompletions.scala | 1 +
src/main/scala/vscextension/statusBar.scala | 24 ++++++++++++++++++-
4 files changed, 29 insertions(+), 2 deletions(-)
diff --git a/README.md b/README.md
index 808fb4b..8511a1b 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# functorcoder
-Open source AI coding assistant "**functorcoder**" is a AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
+ **functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
features:
- code generation: completion, documentation
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 909fab4..441cede 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -13,6 +13,7 @@ import vscextension.facade.vscodeUtils.showMessageAndLog
import functorcoder.types.editorCtx.codeActionParam
import functorcoder.llm.llmMain.llmAgent
import functorcoder.algo.treeParse
+import vscextension.statusBar
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
@@ -45,6 +46,9 @@ object Commands {
val param =
arg.asInstanceOf[codeActionParam[Future[String]]]
val llmResponse = param.param
+
+ statusBar.showSpininngStatusBarItem("functorcoder", llmResponse)
+
llmResponse.foreach { response =>
showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
// apply the changes to the document
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index be246cc..d4e3262 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -46,6 +46,7 @@ object inlineCompletions {
)
}.toJSPromise
+ statusBar.showSpininngStatusBarItem("functorcoder", providerResultF)
providerResultF.asInstanceOf[typings.vscode.mod.ProviderResult[
scala.scalajs.js.Array[typings.vscode.mod.InlineCompletionItem] | typings.vscode.mod.InlineCompletionList
]]
diff --git a/src/main/scala/vscextension/statusBar.scala b/src/main/scala/vscextension/statusBar.scala
index d8c7a7b..c85a2c1 100644
--- a/src/main/scala/vscextension/statusBar.scala
+++ b/src/main/scala/vscextension/statusBar.scala
@@ -1,9 +1,13 @@
package vscextension
+import scala.scalajs.js
import typings.vscode.mod as vscode
import vscextension.facade.vscodeUtils.*
-
import functorcoder.actions.Commands
+import scala.concurrent.Future
+import scala.scalajs.js.JSConverters.JSRichFutureNonThenable
+import scala.concurrent.ExecutionContext.Implicits.global
+
object statusBar {
def createStatusBarItem(context: vscode.ExtensionContext) = {
@@ -19,4 +23,22 @@ object statusBar {
context.pushDisposable(statusBarItem.asInstanceOf[vscode.Disposable])
statusBarItem
}
+
+ /** Show a spinning status bar item while loading
+ * @param text
+ * the text to show
+ * @param promise
+ * the promise to wait for
+ */
+ def showSpininngStatusBarItem(text: String, promise: js.Promise[Any]): vscode.Disposable = {
+ // show a spinner while loading
+ vscode.window.setStatusBarMessage(
+ "$(sync~spin)" + text,
+ hideWhenDone = promise.asInstanceOf[typings.std.PromiseLike[Any]]
+ )
+ }
+
+ def showSpininngStatusBarItem(text: String, future: Future[Any]): vscode.Disposable = {
+ showSpininngStatusBarItem(text, future.toJSPromise)
+ }
}
From 019fe5c6d8d1d4de89682d8d76a2f78261a2222c Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 30 Mar 2025 17:47:43 +0200
Subject: [PATCH 23/46] Update README for clarity on features and current
status; change command references in package.json to 'functorcoder.menu'
---
README.md | 8 ++++++--
package.json | 4 ++--
2 files changed, 8 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index 8511a1b..013e312 100644
--- a/README.md
+++ b/README.md
@@ -1,7 +1,7 @@
# functorcoder
- **functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
+**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
-features:
+features aiming to implement:
- code generation: completion, documentation
- code modification: refactoring, optimization, bug fixing
- code analysis: code understanding, code review, code quality
@@ -24,6 +24,10 @@ The Output:
- transformation: the transformation of the input code
- suggestion: a suggestion for debugging or improvement or refactoring
+## current status
+features implemented:
+- auto completion
+- add documentation quick fix action
## Project Structure
package name: com.functorcoder
diff --git a/package.json b/package.json
index 33fdfe9..11d386b 100644
--- a/package.json
+++ b/package.json
@@ -53,13 +53,13 @@
"menus": {
"file/newFile": [
{
- "command": "extension.helloWorld",
+ "command": "functorcoder.menu",
"group": "navigation"
}
],
"editor/context": [
{
- "command": "extension.helloWorld",
+ "command": "functorcoder.menu",
"group": "1_modification"
}
]
From 7bcd2957fb571be2a5dfd855cf7c2f27292ffbe9 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 1 Apr 2025 13:19:04 +0200
Subject: [PATCH 24/46] Refactor build configuration and enhance logging; add
printlnOrange function for colored output and improve status bar messaging
with language context
---
build.sbt | 36 +++++++++----------
.../scala/functorcoder/actions/Commands.scala | 2 +-
src/main/scala/vscextension/CodeActions.scala | 4 ++-
3 files changed, 21 insertions(+), 21 deletions(-)
diff --git a/build.sbt b/build.sbt
index e3b8a56..7afb4b6 100644
--- a/build.sbt
+++ b/build.sbt
@@ -36,7 +36,6 @@ lazy val root = project
),
libraryDependencies ++= Seq(
// "com.lihaoyi" %%% "utest" % "0.8.2" % "test",
- // ("org.latestbit", "circe-tagged-adt-codec", "0.11.0")
"org.latestbit" %%% "circe-tagged-adt-codec" % "0.11.0",
"com.github.doofin.stdScala" %%% "stdscala" % "387b33df3a",
@@ -74,7 +73,12 @@ addCommandAlias("compile", ";fastOptJS")
addCommandAlias("dev", "~buildDebug")
addCommandAlias("fix", ";scalafixEnable;scalafixAll;")
// open, buildDebug are other commands added
-
+/** prepare the extension and open vscode in extensionDevelopmentPath
+ *
+ * @param openVscode
+ * whether to open vscode or not. If false, it will just prepare the extension
+ * @return
+ */
def openVSCodeTask(openVscode: Boolean = true): Def.Initialize[Task[Unit]] =
Def
.task[Unit] {
@@ -82,34 +86,28 @@ def openVSCodeTask(openVscode: Boolean = true): Def.Initialize[Task[Unit]] =
val log = (ThisProject / streams).value.log
val path = base.getCanonicalPath
+
+ printlnOrange("[compiling] extension")
+ val _ = (Compile / fastOptJS).value
// install deps to out dir
- // print info with orange color
- println("\u001b[33m" + "[copying] package.json to out dir" + "\u001b[0m")
+ printlnOrange("[copying] package.json to out dir")
s"cp package.json ${outdir}/package.json" ! log
if (!(base / outdir / "node_modules").exists) {
- println("\u001b[33m" + "[installing] dependencies into out dir with npm" + "\u001b[0m")
+ printlnOrange("[installing] dependencies into out dir with npm")
s"npm install --prefix ${outdir}" ! log
} else {
- println("\u001b[33m" + "[skipping] dependencies installation" + "\u001b[0m")
+ printlnOrange("[skipping] dependencies installation")
}
// launch vscode
if (openVscode) {
val extenPath = s"${path}/${outdir}"
- println("\u001b[33m" + "[opening] vscode" + "\u001b[0m")
- println("\u001b[33m" + s"with extensionDevelopmentPath=${extenPath}" + "\u001b[0m")
+ printlnOrange("[opening] vscode")
+ printlnOrange(s"with extensionDevelopmentPath=${extenPath}")
s"code --extensionDevelopmentPath=$extenPath" ! log
}
()
}
-/* lazy val installDependencies = Def.task[Unit] {
- val base = (ThisProject / baseDirectory).value
- val log = (ThisProject / streams).value.log
- if (!(base / "node_module").exists) {
- val pb =
- new java.lang.ProcessBuilder("npm", "install")
- .directory(base)
- .redirectErrorStream(true)
- pb ! log
- }
-} */
+def printlnOrange(msg: Any): Unit = {
+ println("\u001b[33m" + msg + "\u001b[0m")
+}
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 441cede..007081e 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -50,7 +50,7 @@ object Commands {
statusBar.showSpininngStatusBarItem("functorcoder", llmResponse)
llmResponse.foreach { response =>
- showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
+ // showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
// apply the changes to the document
vscode.window.activeTextEditor.toOption match {
case None =>
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 276305e..bd2fef0 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -30,14 +30,16 @@ object CodeActions {
context: vscode.CodeActionContext
) = {
val selectedCode = document.getText(range)
+ val language = documentProps.getLanguage()
val llmResponse =
llm.sendPrompt(
llmPrompt.Modification(
code = selectedCode, //
- taskRequirement = llmPrompt.generateDocs(documentProps.getLanguage())
+ taskRequirement = llmPrompt.generateDocs(language)
)
)
+ statusBar.showSpininngStatusBarItem(s"functorcoder($language)", llmResponse)
val fix1 =
new vscode.CodeAction(
title = "add documentation for selected code",
From b92d8f5c9d684cc4ffe5f1189721a7752ad05405 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 1 Apr 2025 21:13:08 +0200
Subject: [PATCH 25/46] Refactor build script and improve logging; update
VSCode task handling and enhance status bar messaging
---
build.sbt | 23 ++++++++-----------
src/main/scala/vscextension/CodeActions.scala | 1 +
.../scala/vscextension/extensionMain.scala | 4 ++--
3 files changed, 12 insertions(+), 16 deletions(-)
diff --git a/build.sbt b/build.sbt
index 7afb4b6..d6c3607 100644
--- a/build.sbt
+++ b/build.sbt
@@ -64,9 +64,6 @@ lazy val root = project
),
open := openVSCodeTask().dependsOn(Compile / fastOptJS).value,
buildDebug := openVSCodeTask(openVscode = false).dependsOn(Compile / fastOptJS).value
- // open := openVSCodeTask.dependsOn(Compile / fastOptJS / webpack).value,
- // testFrameworks += new TestFramework("utest.runner.Framework")
- // publishMarketplace := publishMarketplaceTask.dependsOn(fullOptJS in Compile).value
)
addCommandAlias("compile", ";fastOptJS")
@@ -82,28 +79,26 @@ addCommandAlias("fix", ";scalafixEnable;scalafixAll;")
def openVSCodeTask(openVscode: Boolean = true): Def.Initialize[Task[Unit]] =
Def
.task[Unit] {
- val base = (ThisProject / baseDirectory).value
- val log = (ThisProject / streams).value.log
-
- val path = base.getCanonicalPath
+ val baseDir = (ThisProject / baseDirectory).value
+ val baseDirPath = baseDir.getCanonicalPath
+ val logger = (ThisProject / streams).value.log
printlnOrange("[compiling] extension")
val _ = (Compile / fastOptJS).value
// install deps to out dir
printlnOrange("[copying] package.json to out dir")
- s"cp package.json ${outdir}/package.json" ! log
- if (!(base / outdir / "node_modules").exists) {
+ s"cp package.json ${outdir}/package.json" ! logger
+ if (!(baseDir / outdir / "node_modules").exists) {
printlnOrange("[installing] dependencies into out dir with npm")
- s"npm install --prefix ${outdir}" ! log
+ s"npm install --prefix ${outdir}" ! logger
} else {
printlnOrange("[skipping] dependencies installation")
}
// launch vscode
if (openVscode) {
- val extenPath = s"${path}/${outdir}"
- printlnOrange("[opening] vscode")
- printlnOrange(s"with extensionDevelopmentPath=${extenPath}")
- s"code --extensionDevelopmentPath=$extenPath" ! log
+ val extensionPath = s"${baseDirPath}/${outdir}"
+ printlnOrange(s"[opening] vscode" + s"with extensionDevelopmentPath=${extensionPath}")
+ s"code --extensionDevelopmentPath=$extensionPath" ! logger
}
()
}
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index bd2fef0..e91884e 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -39,6 +39,7 @@ object CodeActions {
)
)
+ // show the spinner when waiting
statusBar.showSpininngStatusBarItem(s"functorcoder($language)", llmResponse)
val fix1 =
new vscode.CodeAction(
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 9cb992b..a2d0522 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -19,7 +19,7 @@ object extensionMain {
val cfg = settings.readConfig()
val llm = functorcoder.llm.llmMain.llmAgent(cfg)
- showMessageAndLog(s"config loaded: ${cfg.toString()}")
+ // showMessageAndLog(s"config loaded: ${cfg.toString()}")
// register all commands
vscCommands.registerAllCommands(context, llm)
@@ -27,7 +27,7 @@ object extensionMain {
statusBar.createStatusBarItem(context)
// statusBarItem.text = "functorcoder ok"
// show the current language of the document
- documentProps.showProps
+ // documentProps.showProps
// register inline completions like github copilot
inlineCompletions.registerInlineCompletions(llm)
From 7324125834ca45775dc92b99468e93b6512a8d0c Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 1 Apr 2025 21:30:11 +0200
Subject: [PATCH 26/46] Refactor command structure and implement a new menu
system; remove deprecated main menu items and enhance quick pick
functionality
---
.../scala/functorcoder/actions/Commands.scala | 5 ---
.../functorcoder/actions/createFiles.scala | 1 -
.../scala/functorcoder/editorUI/menu.scala | 21 ++++++++++
.../vscextension/facade/InlineEdit.scala | 1 -
src/main/scala/vscextension/quickPick.scala | 39 ++++++++++---------
5 files changed, 42 insertions(+), 25 deletions(-)
create mode 100644 src/main/scala/functorcoder/editorUI/menu.scala
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 007081e..a433e9c 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -36,11 +36,6 @@ object Commands {
(commandCreateFiles._1, commandCreateFiles._2(llm))
)
- // the main menu items
- val mainMenuItems: Seq[(String, () => Unit)] = Seq(
- "create files" -> { () => println("create files") }
- )
-
// individual command handlers
def addDocumentation(arg: Any) = {
val param =
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
index 2d836db..d33b270 100644
--- a/src/main/scala/functorcoder/actions/createFiles.scala
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -1,6 +1,5 @@
package functorcoder.actions
-import scala.collection.mutable.ArrayBuffer
import com.doofin.stdScala.dataTypes.Tree.TreeNode
import functorcoder.algo.treeParse
import vscextension.facade.vscodeUtils.showMessageAndLog
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
new file mode 100644
index 0000000..7f2e556
--- /dev/null
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -0,0 +1,21 @@
+package functorcoder.editorUI
+
+import vscextension.facade.vscodeUtils.*
+
+object menu {
+ case class Menu(
+ title: String, //
+ menuItems: Seq[(String, () => Unit)]
+ )
+ // the menu items
+ val mainMenuItems: Seq[(String, () => Unit)] = Seq(
+ "create files" -> { () => showMessageAndLog("create files") },
+ "disable autocomplete" -> { () => showMessageAndLog("disable autocomplete") }
+ )
+
+ // the main menu
+ val myMenu = Menu(
+ title = "functorcoder menu",
+ menuItems = mainMenuItems
+ )
+}
diff --git a/src/main/scala/vscextension/facade/InlineEdit.scala b/src/main/scala/vscextension/facade/InlineEdit.scala
index 5dd64c7..45f02a1 100644
--- a/src/main/scala/vscextension/facade/InlineEdit.scala
+++ b/src/main/scala/vscextension/facade/InlineEdit.scala
@@ -5,7 +5,6 @@ import scala.scalajs.js
import typings.vscode.mod as vscode
import typings.vscode.mod.Command
-import scala.scalajs.js.Promise
/** a dialog in the editor that users can accept or reject
*
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 3374a13..5a2d227 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -1,13 +1,10 @@
package vscextension
-import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
import scala.scalajs.js.JSConverters._
import typings.vscode.mod as vscode
-import facade.vscodeUtils.*
-import functorcoder.actions.Commands
/** Show a quick pick palette to select items in multiple steps
*
@@ -56,30 +53,35 @@ object quickPick {
}
def showQuickPick(arg: Any): Unit = {
- val items =
- Commands.mainMenuItems.map(_._1).toJSArray
+ val mMenu = functorcoder.editorUI.menu.myMenu
val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
vscode.window.createQuickPick()
- quickPick.title = "Quick Pick"
- quickPick.placeholder = "pick one item"
- quickPick.totalSteps = 3
+ quickPick.title = mMenu.title
+ quickPick.placeholder = "select an action"
+ // quickPick.totalSteps = 3
quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
- // option items for user to pick
- quickPick.items = items.map { itemStr => //
+ // set the items in the quick pick
+ quickPick.items = mMenu.menuItems.map(_._1).toJSArray.map { itemStr => //
vscode
- .QuickPickItem(itemStr)
- .setAlwaysShow(true)
- .setButtons(js.Array(vscode.QuickInputButtons.Back))
- .setDescription(itemStr + " description")
- .setDetail(itemStr + " detail")
+ .QuickPickItem(itemStr) // label is itemStr
+ // .setAlwaysShow(true)
+ // .setButtons(js.Array(vscode.QuickInputButtons.Back))
+ .setDescription(itemStr)
+ // .setDetail(itemStr + " detail")
}
quickPick.onDidChangeSelection { selection =>
- println(s"selected: ${selection(0).label}")
- if (selection(0).label == "item1") {
+ val selectedLabel = selection(0).label
+ // execute the function associated with the selected item
+ mMenu.menuItems.find(_._1 == selectedLabel).foreach { (_, fun) =>
+ fun()
+ quickPick.hide()
+ }
+
+ /* if (selection(0).label == "item1") {
println(s"selected: ${selection(0).label}")
// show another input box after selecting item1
@@ -93,7 +95,8 @@ object quickPick {
showMessage("input: " + input)
}
- }
+ } */
+
}
quickPick.onDidHide({ _ =>
From 174509d105581f6b6754c6ae00c1a9ca9de89c7f Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 2 Apr 2025 22:34:23 +0200
Subject: [PATCH 27/46] Refactor command names for consistency; update input
box titles and improve quick pick functionality
---
package.json | 2 +-
.../scala/functorcoder/actions/Commands.scala | 48 ++++++++++++++-----
.../scala/functorcoder/actions/Debug.scala | 1 +
.../functorcoder/actions/createFiles.scala | 10 ++++
.../scala/functorcoder/editorUI/menu.scala | 13 ++++-
src/main/scala/vscextension/CodeActions.scala | 2 +-
.../scala/vscextension/extensionMain.scala | 4 +-
src/main/scala/vscextension/quickPick.scala | 47 +++++++++++-------
src/main/scala/vscextension/statusBar.scala | 2 +-
9 files changed, 93 insertions(+), 36 deletions(-)
diff --git a/package.json b/package.json
index 11d386b..88f0446 100644
--- a/package.json
+++ b/package.json
@@ -47,7 +47,7 @@
},
{
"command": "functorcoder.createFiles",
- "title": "generate files and folders"
+ "title": "create files and folders"
}
],
"menus": {
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index a433e9c..10c9ef0 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -20,20 +20,20 @@ import vscextension.statusBar
object Commands {
type CommandT = Any => Any
// all the commands here
- val commandMenu =
+ val cmdShowMenu =
("functorcoder.menu", quickPick.showQuickPick)
- val commandAddDocumentation =
+ val cmdAddDocs =
("functorcoder.addDocumentation", addDocumentation)
- val commandCreateFiles =
+ val cmdCreateFiles =
("functorcoder.createFiles", createFilesCmd)
// list of all commands to be registered
def commandList(llm: llmAgent): Seq[(String, CommandT)] =
Seq(
- commandMenu,
- commandAddDocumentation,
- (commandCreateFiles._1, commandCreateFiles._2(llm))
+ cmdShowMenu,
+ cmdAddDocs,
+ (cmdCreateFiles._1, cmdCreateFiles._2(llm))
)
// individual command handlers
@@ -62,13 +62,35 @@ object Commands {
}
def createFilesCmd(llm: llmAgent)(arg: Any) = {
- val inputBoxOptions =
- vscode
- .InputBoxOptions()
- .setTitle("generate files and folders")
- .setPlaceHolder("type your description here")
+ quickPick.createInputBox(
+ title = "Create files/folders description",
+ placeHolder = "describe your project",
+ onInput = { input =>
+ llm
+ .sendPrompt(
+ functorcoder.llm.llmPrompt.CreateFiles(input)
+ )
+ .foreach { response =>
+ showMessageAndLog("create files: " + s"${response}")
+ val tree = treeParse.parse(response)
+ quickPick.createQuickPick(
+ title = "Files and Folders",
+ placeHolder = "select to apply creation",
+ items = Seq(
+ (
+ "files created!",
+ tree.toString,
+ { () =>
+ showMessageAndLog("files created!")
+ }
+ )
+ )
+ )
+ }
+ }
+ )
- for {
+ /* for {
input <- vscode.window.showInputBox(inputBoxOptions).toFuture
response <- llm.sendPrompt(
functorcoder.llm.llmPrompt.CreateFiles(input match {
@@ -91,6 +113,6 @@ object Commands {
)
)
)
- }
+ } */
}
}
diff --git a/src/main/scala/functorcoder/actions/Debug.scala b/src/main/scala/functorcoder/actions/Debug.scala
index fe4de50..b247841 100644
--- a/src/main/scala/functorcoder/actions/Debug.scala
+++ b/src/main/scala/functorcoder/actions/Debug.scala
@@ -1 +1,2 @@
package functorcoder.actions
+object Debug {}
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
index d33b270..94c5e7b 100644
--- a/src/main/scala/functorcoder/actions/createFiles.scala
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -8,6 +8,16 @@ import vscextension.facade.vscodeUtils.showMessageAndLog
*/
object createFiles {
+ /** parse the prompt response to list of files and folders
+ *
+ * The prompt response is like: [(dir1/file1,"content1"), (dir2/file2,"content2")]
+ *
+ * it should return list like: List((dir1/file1, "content1"), (dir2/file2, "content2"))
+ * @param promptResponse
+ * the response from the prompt
+ */
+ def parseFilesList(promptResponse: String, retry: Int = 3): Unit = {}
+
/** parse the prompt response to tree of files and folders
*
* The prompt response is like: (root [(folder1 [(file1 file2) folder2]) folder3])
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
index 7f2e556..ee2bb5c 100644
--- a/src/main/scala/functorcoder/editorUI/menu.scala
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -1,6 +1,8 @@
package functorcoder.editorUI
+import typings.vscode.mod as vscode
import vscextension.facade.vscodeUtils.*
+import vscextension.quickPick
object menu {
case class Menu(
@@ -9,7 +11,16 @@ object menu {
)
// the menu items
val mainMenuItems: Seq[(String, () => Unit)] = Seq(
- "create files" -> { () => showMessageAndLog("create files") },
+ "create files" -> { () =>
+ quickPick.createInputBox(
+ title = "Create files/folders description",
+ placeHolder = "describe your project",
+ onInput = { input =>
+ showMessageAndLog("input: " + input)
+ }
+ )
+
+ },
"disable autocomplete" -> { () => showMessageAndLog("disable autocomplete") }
)
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index e91884e..79c3851 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -55,7 +55,7 @@ object CodeActions {
// invoke command
command = vscode
.Command(
- command = functorcoder.actions.Commands.commandAddDocumentation._1, //
+ command = functorcoder.actions.Commands.cmdAddDocs._1, //
title = "add documentation" //
)
.setArguments(js.Array(args))
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index a2d0522..4051f29 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -13,9 +13,9 @@ object extensionMain {
*/
@JSExportTopLevel("activate") // Exports the function to javascript so that VSCode can load it
def activate(context: vscode.ExtensionContext): Unit = {
- showMessageAndLog("congrats, your scala.js vscode extension is loaded")
+ // showMessageAndLog("congrats, your scala.js vscode extension is loaded")
- vscode.workspace.rootPath.getOrElse("")
+ // vscode.workspace.rootPath.getOrElse("")
val cfg = settings.readConfig()
val llm = functorcoder.llm.llmMain.llmAgent(cfg)
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 5a2d227..8cc9f8e 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -2,10 +2,10 @@ package vscextension
import scala.scalajs.js
import scala.scalajs.js.JSConverters._
+import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
-
/** Show a quick pick palette to select items in multiple steps
*
* similar to the command palette in vscode
@@ -14,6 +14,7 @@ object quickPick {
def createQuickPick(
title: String, //
+ placeHolder: String,
items: Seq[(String, String, () => Unit)],
modifies: vscode.QuickPick[vscode.QuickPickItem] => Unit = { _ => }
) = {
@@ -22,6 +23,7 @@ object quickPick {
vscode.window.createQuickPick()
quickPick.title = title
+ quickPick.placeholder = placeHolder
// to customize the quick pick
modifies(quickPick)
quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
@@ -81,22 +83,6 @@ object quickPick {
quickPick.hide()
}
- /* if (selection(0).label == "item1") {
- println(s"selected: ${selection(0).label}")
-
- // show another input box after selecting item1
- val inputBoxOptions =
- vscode
- .InputBoxOptions()
- .setTitle("Input Box")
- .setPlaceHolder("type something")
-
- vscode.window.showInputBox(inputBoxOptions).toFuture.foreach { input =>
- showMessage("input: " + input)
- }
-
- } */
-
}
quickPick.onDidHide({ _ =>
@@ -106,4 +92,31 @@ object quickPick {
quickPick.show()
}
+ /** create an input box for string
+ *
+ * @param title
+ * the title of the input box
+ * @param placeHolder
+ * the placeholder text
+ * @param onInput
+ * the function to call when input is received
+ */
+ def createInputBox(
+ title: String,
+ placeHolder: String,
+ onInput: String => Unit
+ ) = {
+ val inputBoxOptions =
+ vscode
+ .InputBoxOptions()
+ .setTitle(title)
+ .setPlaceHolder(placeHolder)
+
+ vscode.window.showInputBox(inputBoxOptions).toFuture.foreach { inputO =>
+ inputO.toOption match {
+ case None =>
+ case Some(input) => onInput(input)
+ }
+ }
+ }
}
diff --git a/src/main/scala/vscextension/statusBar.scala b/src/main/scala/vscextension/statusBar.scala
index c85a2c1..fc5deef 100644
--- a/src/main/scala/vscextension/statusBar.scala
+++ b/src/main/scala/vscextension/statusBar.scala
@@ -17,7 +17,7 @@ object statusBar {
val name = "functor"
statusBarItem.text = name
statusBarItem.name = name
- statusBarItem.command = Commands.commandMenu._1
+ statusBarItem.command = Commands.cmdShowMenu._1
statusBarItem.show()
context.pushDisposable(statusBarItem.asInstanceOf[vscode.Disposable])
From dfff270cabea7c5188df416cb5866b6382dafc10 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 6 Apr 2025 23:42:24 +0200
Subject: [PATCH 28/46] Refactor command handling to integrate llmAgent; update
menu display and status bar item creation
---
src/main/scala/functorcoder/actions/Commands.scala | 4 ++--
src/main/scala/functorcoder/editorUI/menu.scala | 11 +++++++----
src/main/scala/vscextension/extensionMain.scala | 2 +-
src/main/scala/vscextension/quickPick.scala | 5 +++--
src/main/scala/vscextension/statusBar.scala | 3 ++-
5 files changed, 15 insertions(+), 10 deletions(-)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 10c9ef0..13c5838 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -21,7 +21,7 @@ object Commands {
type CommandT = Any => Any
// all the commands here
val cmdShowMenu =
- ("functorcoder.menu", quickPick.showQuickPick)
+ ("functorcoder.menu", quickPick.showMainMenu)
val cmdAddDocs =
("functorcoder.addDocumentation", addDocumentation)
@@ -31,7 +31,7 @@ object Commands {
// list of all commands to be registered
def commandList(llm: llmAgent): Seq[(String, CommandT)] =
Seq(
- cmdShowMenu,
+ (cmdShowMenu._1, cmdShowMenu._2(llm)),
cmdAddDocs,
(cmdCreateFiles._1, cmdCreateFiles._2(llm))
)
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
index ee2bb5c..cc2c8e0 100644
--- a/src/main/scala/functorcoder/editorUI/menu.scala
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -3,6 +3,7 @@ package functorcoder.editorUI
import typings.vscode.mod as vscode
import vscextension.facade.vscodeUtils.*
import vscextension.quickPick
+import functorcoder.llm.llmMain.llmAgent
object menu {
case class Menu(
@@ -25,8 +26,10 @@ object menu {
)
// the main menu
- val myMenu = Menu(
- title = "functorcoder menu",
- menuItems = mainMenuItems
- )
+ def getMainMenu(llm: llmAgent) = {
+ Menu(
+ title = "functorcoder menu",
+ menuItems = mainMenuItems
+ )
+ }
}
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 4051f29..d498c65 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -24,7 +24,7 @@ object extensionMain {
vscCommands.registerAllCommands(context, llm)
// show the status bar
- statusBar.createStatusBarItem(context)
+ statusBar.createStatusBarItem(context, llm)
// statusBarItem.text = "functorcoder ok"
// show the current language of the document
// documentProps.showProps
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 8cc9f8e..d60f051 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -5,6 +5,7 @@ import scala.scalajs.js.JSConverters._
import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
+import functorcoder.llm.llmMain.llmAgent
/** Show a quick pick palette to select items in multiple steps
*
@@ -54,8 +55,8 @@ object quickPick {
quickPick.show()
}
- def showQuickPick(arg: Any): Unit = {
- val mMenu = functorcoder.editorUI.menu.myMenu
+ def showMainMenu(llm: llmAgent)(arg: Any): Unit = {
+ val mMenu = functorcoder.editorUI.menu.getMainMenu(llm)
val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
vscode.window.createQuickPick()
diff --git a/src/main/scala/vscextension/statusBar.scala b/src/main/scala/vscextension/statusBar.scala
index fc5deef..2458350 100644
--- a/src/main/scala/vscextension/statusBar.scala
+++ b/src/main/scala/vscextension/statusBar.scala
@@ -7,10 +7,11 @@ import functorcoder.actions.Commands
import scala.concurrent.Future
import scala.scalajs.js.JSConverters.JSRichFutureNonThenable
import scala.concurrent.ExecutionContext.Implicits.global
+import functorcoder.llm.llmMain.llmAgent
object statusBar {
- def createStatusBarItem(context: vscode.ExtensionContext) = {
+ def createStatusBarItem(context: vscode.ExtensionContext, llm: llmAgent) = {
val statusBarItem =
vscode.window.createStatusBarItem(vscode.StatusBarAlignment.Right)
From 1d42270d470fd707b62d5f586b02647cefd61cb2 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 8 Apr 2025 23:57:12 +0200
Subject: [PATCH 29/46] Refactor create files command to streamline input
handling; update main menu to invoke command directly and enhance quick pick
functionality
---
.../scala/functorcoder/actions/Commands.scala | 59 ++++++-------------
.../scala/functorcoder/editorUI/menu.scala | 23 +++-----
src/main/scala/vscextension/quickPick.scala | 54 ++++++-----------
3 files changed, 46 insertions(+), 90 deletions(-)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 13c5838..82d8c1f 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -66,53 +66,30 @@ object Commands {
title = "Create files/folders description",
placeHolder = "describe your project",
onInput = { input =>
- llm
- .sendPrompt(
+ val respFuture =
+ llm.sendPrompt(
functorcoder.llm.llmPrompt.CreateFiles(input)
)
- .foreach { response =>
- showMessageAndLog("create files: " + s"${response}")
- val tree = treeParse.parse(response)
- quickPick.createQuickPick(
- title = "Files and Folders",
- placeHolder = "select to apply creation",
- items = Seq(
- (
- "files created!",
- tree.toString,
- { () =>
- showMessageAndLog("files created!")
- }
- )
+
+ respFuture.foreach { response =>
+ // parse the response to a tree of files and folders
+ val tree = treeParse.parse(response)
+ quickPick.createQuickPick(
+ title = "Files and Folders",
+ placeHolder = "select to apply creation",
+ items = Seq(
+ (
+ "files created!",
+ tree.toString,
+ { () =>
+ showMessageAndLog("files created!")
+ }
)
)
- }
+ )
+ }
}
)
- /* for {
- input <- vscode.window.showInputBox(inputBoxOptions).toFuture
- response <- llm.sendPrompt(
- functorcoder.llm.llmPrompt.CreateFiles(input match {
- case _: Unit => "empty input!"
- case s: String => s
- })
- )
- } yield {
- showMessageAndLog("create files: " + s"${response}")
- val tree = treeParse.parse(response)
- quickPick.createQuickPick(
- title = "Files and Folders",
- items = Seq(
- (
- "files created!",
- tree.toString,
- { () =>
- showMessageAndLog("files created!")
- }
- )
- )
- )
- } */
}
}
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
index cc2c8e0..5b23d11 100644
--- a/src/main/scala/functorcoder/editorUI/menu.scala
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -4,29 +4,24 @@ import typings.vscode.mod as vscode
import vscextension.facade.vscodeUtils.*
import vscextension.quickPick
import functorcoder.llm.llmMain.llmAgent
+import functorcoder.actions.Commands
+import cats.syntax.show
object menu {
case class Menu(
title: String, //
menuItems: Seq[(String, () => Unit)]
)
- // the menu items
- val mainMenuItems: Seq[(String, () => Unit)] = Seq(
- "create files" -> { () =>
- quickPick.createInputBox(
- title = "Create files/folders description",
- placeHolder = "describe your project",
- onInput = { input =>
- showMessageAndLog("input: " + input)
- }
- )
-
- },
- "disable autocomplete" -> { () => showMessageAndLog("disable autocomplete") }
- )
// the main menu
def getMainMenu(llm: llmAgent) = {
+ val mainMenuItems: Seq[(String, () => Unit)] = Seq(
+ "create files" -> { () =>
+ // invoke the create files command directly as function
+ val _: Unit = Commands.cmdCreateFiles._2(llm)(())
+ },
+ "disable autocomplete" -> { () => showMessageAndLog("disable autocomplete") }
+ )
Menu(
title = "functorcoder menu",
menuItems = mainMenuItems
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index d60f051..3dfd8cb 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -13,11 +13,22 @@ import functorcoder.llm.llmMain.llmAgent
*/
object quickPick {
+ /** create a quick pick with a list of items
+ *
+ * @param title
+ * the title of the quick pick
+ * @param placeHolder
+ * the placeholder text
+ * @param items
+ * (label, description, function) a list of items to show in the quick pick
+ * @param modifieF
+ * a function to modify the quick pick (e.g. add buttons)
+ */
def createQuickPick(
title: String, //
placeHolder: String,
items: Seq[(String, String, () => Unit)],
- modifies: vscode.QuickPick[vscode.QuickPickItem] => Unit = { _ => }
+ modifieF: vscode.QuickPick[vscode.QuickPickItem] => Unit = { _ => }
) = {
val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
@@ -26,7 +37,7 @@ object quickPick {
quickPick.title = title
quickPick.placeholder = placeHolder
// to customize the quick pick
- modifies(quickPick)
+ modifieF(quickPick)
quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
quickPick.items = items.toJSArray.map { (itemStr, itemDesc, _) => //
@@ -53,44 +64,17 @@ object quickPick {
})
quickPick.show()
+ quickPick
}
def showMainMenu(llm: llmAgent)(arg: Any): Unit = {
val mMenu = functorcoder.editorUI.menu.getMainMenu(llm)
- val quickPick: vscode.QuickPick[vscode.QuickPickItem] =
- vscode.window.createQuickPick()
-
- quickPick.title = mMenu.title
- quickPick.placeholder = "select an action"
- // quickPick.totalSteps = 3
- quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
-
- // set the items in the quick pick
- quickPick.items = mMenu.menuItems.map(_._1).toJSArray.map { itemStr => //
- vscode
- .QuickPickItem(itemStr) // label is itemStr
- // .setAlwaysShow(true)
- // .setButtons(js.Array(vscode.QuickInputButtons.Back))
- .setDescription(itemStr)
- // .setDetail(itemStr + " detail")
- }
-
- quickPick.onDidChangeSelection { selection =>
- val selectedLabel = selection(0).label
- // execute the function associated with the selected item
- mMenu.menuItems.find(_._1 == selectedLabel).foreach { (_, fun) =>
- fun()
- quickPick.hide()
- }
-
- }
-
- quickPick.onDidHide({ _ =>
- quickPick.hide()
- })
-
- quickPick.show()
+ createQuickPick(
+ title = mMenu.title,
+ placeHolder = "select an action",
+ items = mMenu.menuItems.map(x => (x._1, x._1, x._2)).toSeq
+ )
}
/** create an input box for string
From fd07593b37bafe842051c27f729071dce2c159af Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 9 Apr 2025 22:02:20 +0200
Subject: [PATCH 30/46] parentPaths for create files
---
.../scala/functorcoder/actions/Commands.scala | 85 +++++++++++++------
.../scala/functorcoder/editorUI/menu.scala | 1 -
src/main/scala/vscextension/CodeActions.scala | 2 +-
.../{documentProps.scala => editorAPI.scala} | 6 +-
src/main/scala/vscextension/quickPick.scala | 4 +-
5 files changed, 67 insertions(+), 31 deletions(-)
rename src/main/scala/vscextension/{documentProps.scala => editorAPI.scala} (83%)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index 82d8c1f..de8fdbf 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -14,6 +14,7 @@ import functorcoder.types.editorCtx.codeActionParam
import functorcoder.llm.llmMain.llmAgent
import functorcoder.algo.treeParse
import vscextension.statusBar
+import vscextension.editorAPI
/** Commands are actions that a user can invoke in the vscode extension with command palette (ctrl+shift+p).
*/
@@ -62,34 +63,66 @@ object Commands {
}
def createFilesCmd(llm: llmAgent)(arg: Any) = {
- quickPick.createInputBox(
- title = "Create files/folders description",
- placeHolder = "describe your project",
- onInput = { input =>
- val respFuture =
- llm.sendPrompt(
- functorcoder.llm.llmPrompt.CreateFiles(input)
- )
+ val currDir = editorAPI.getCurrentDirectory()
+
+ currDir match {
+ case None =>
+ showMessageAndLog("no current directory, please open a file")
+ case Some(value) =>
+ // split the path
+ val pathParts = value.split("/")
+ // generate the full path for parent and 1 to 5 levels up
+ val parentPaths =
+ (1 to 5).map { i =>
+ pathParts.take(pathParts.length - i).mkString("/")
+ }
+ showMessageAndLog(
+ "parent paths: " + parentPaths.mkString(", ")
+ )
+ quickPick.createQuickPick(
+ title = "create files/folders",
+ placeHolder = "select a parent folder",
+ items = parentPaths.map { path =>
+ (
+ path,
+ "",
+ { () =>
+ // create the files and folders according to the tree
+ showMessageAndLog("creating files in: " + path)
+ quickPick.createInputBox(
+ title = "Create files/folders description",
+ placeHolder = "describe your project",
+ onInput = { input =>
+ val respFuture = llm.sendPrompt(functorcoder.llm.llmPrompt.CreateFiles(input))
+ respFuture.foreach { response =>
+ // parse the response to a tree of files and folders
+ val tree = treeParse.parse(response)
+ showMessageAndLog("current directory: " + currDir)
- respFuture.foreach { response =>
- // parse the response to a tree of files and folders
- val tree = treeParse.parse(response)
- quickPick.createQuickPick(
- title = "Files and Folders",
- placeHolder = "select to apply creation",
- items = Seq(
- (
- "files created!",
- tree.toString,
- { () =>
- showMessageAndLog("files created!")
- }
- )
+ quickPick.createQuickPick(
+ title = "Files and Folders",
+ placeHolder = "select to apply creating files and folders",
+ items = Seq(
+ (
+ "files created!",
+ "",
+ { () =>
+ // create the files and folders according to the tree
+ tree.toString
+ showMessageAndLog("files created!")
+ }
+ )
+ )
+ )
+ }
+ }
+ )
+
+ }
)
- )
- }
- }
- )
+ }
+ )
+ }
}
}
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
index 5b23d11..4d7d0ff 100644
--- a/src/main/scala/functorcoder/editorUI/menu.scala
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -5,7 +5,6 @@ import vscextension.facade.vscodeUtils.*
import vscextension.quickPick
import functorcoder.llm.llmMain.llmAgent
import functorcoder.actions.Commands
-import cats.syntax.show
object menu {
case class Menu(
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 79c3851..ab86523 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -30,7 +30,7 @@ object CodeActions {
context: vscode.CodeActionContext
) = {
val selectedCode = document.getText(range)
- val language = documentProps.getLanguage()
+ val language = editorAPI.getLanguage()
val llmResponse =
llm.sendPrompt(
llmPrompt.Modification(
diff --git a/src/main/scala/vscextension/documentProps.scala b/src/main/scala/vscextension/editorAPI.scala
similarity index 83%
rename from src/main/scala/vscextension/documentProps.scala
rename to src/main/scala/vscextension/editorAPI.scala
index ade4f08..15cf660 100644
--- a/src/main/scala/vscextension/documentProps.scala
+++ b/src/main/scala/vscextension/editorAPI.scala
@@ -5,7 +5,7 @@ import typings.vscode.mod.TextEditor
import facade.vscodeUtils.*
-object documentProps {
+object editorAPI {
/** Shows various properties of the current document and editor
*
@@ -27,4 +27,8 @@ object documentProps {
editor.document.languageId
}
}
+
+ def getCurrentDirectory() = {
+ vscode.window.activeTextEditor.toOption.map(_.document.uri.path)
+ }
}
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index 3dfd8cb..c0074d3 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -45,8 +45,8 @@ object quickPick {
.QuickPickItem(itemStr)
.setAlwaysShow(true)
.setButtons(js.Array(vscode.QuickInputButtons.Back))
- .setDescription(itemStr + " description")
- .setDetail(itemDesc + " detail")
+ .setDescription(itemStr)
+ .setDetail(itemDesc)
}
quickPick.onDidChangeSelection { selection =>
From 741f071f63466db10f3604f2dcc513e52ed2750d Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Thu, 10 Apr 2025 22:34:47 +0200
Subject: [PATCH 31/46] createFiles show file list ok
---
.../scala/functorcoder/actions/Commands.scala | 29 ++++++++++++-------
.../functorcoder/actions/createFiles.scala | 20 +++++++++++--
.../scala/functorcoder/llm/wk.worksheet.sc | 3 ++
src/main/scala/vscextension/quickPick.scala | 5 ++--
4 files changed, 41 insertions(+), 16 deletions(-)
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index de8fdbf..b283b29 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -71,14 +71,12 @@ object Commands {
case Some(value) =>
// split the path
val pathParts = value.split("/")
- // generate the full path for parent and 1 to 5 levels up
+ // generate parent path for and 1 to 5 levels up
val parentPaths =
(1 to 5).map { i =>
pathParts.take(pathParts.length - i).mkString("/")
}
- showMessageAndLog(
- "parent paths: " + parentPaths.mkString(", ")
- )
+
quickPick.createQuickPick(
title = "create files/folders",
placeHolder = "select a parent folder",
@@ -90,26 +88,37 @@ object Commands {
// create the files and folders according to the tree
showMessageAndLog("creating files in: " + path)
quickPick.createInputBox(
- title = "Create files/folders description",
+ title = "Create files/folders under " + path,
placeHolder = "describe your project",
onInput = { input =>
val respFuture = llm.sendPrompt(functorcoder.llm.llmPrompt.CreateFiles(input))
respFuture.foreach { response =>
// parse the response to a tree of files and folders
- val tree = treeParse.parse(response)
- showMessageAndLog("current directory: " + currDir)
+ val treeOpt = treeParse.parse(response)
+ val filesList = treeOpt.map(createFiles.tree2list).getOrElse(Seq()).mkString(", ")
quickPick.createQuickPick(
title = "Files and Folders",
placeHolder = "select to apply creating files and folders",
items = Seq(
(
- "files created!",
+ s"create $filesList",
"",
{ () =>
+ treeOpt match {
+ case scala.util.Success(tree) =>
+ createFiles.createFilesAndFolders(
+ tree,
+ path
+ )
+ case scala.util.Failure(exception) =>
+ showMessageAndLog(
+ s"Failed to parse tree: ${treeOpt.toString}, exception: ${exception.getMessage}"
+ )
+ }
// create the files and folders according to the tree
- tree.toString
- showMessageAndLog("files created!")
+
+ // showMessageAndLog("files created!")
}
)
)
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
index 94c5e7b..7b4343f 100644
--- a/src/main/scala/functorcoder/actions/createFiles.scala
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -32,7 +32,7 @@ object createFiles {
treeParse.parse(promptResponse) match {
case scala.util.Success(tree) =>
- createFilesAndFolders(tree)
+ createFilesAndFolders(tree, "")
case scala.util.Failure(exception) =>
showMessageAndLog(s"Trying again with $retry retries left")
if (retry > 0) {
@@ -47,12 +47,26 @@ object createFiles {
* @param tree
* the tree of files and folders
*/
- def createFilesAndFolders(tree: TreeNode[String]): Unit = {
+ def createFilesAndFolders(tree: TreeNode[String], parentPath0: String): Unit = {
// recursively create files and folders
showMessageAndLog(s"Files and folders tree: $tree")
val TreeNode(root, children) = tree
+ val parentPath: String = parentPath0 + "/" + root
+
children.foreach { child =>
- createFilesAndFolders(child)
+ val file = child.value
+ showMessageAndLog(s"Creating file in $parentPath, file: $file")
+ createFilesAndFolders(child, parentPath)
+ }
+ }
+
+ def tree2list(tree: TreeNode[String]): Seq[String] = {
+ val TreeNode(root, children) = tree
+ val childList = children.flatMap(tree2list)
+ if (childList.isEmpty) {
+ Seq(root)
+ } else {
+ Seq(root) ++ childList
}
}
}
diff --git a/src/main/scala/functorcoder/llm/wk.worksheet.sc b/src/main/scala/functorcoder/llm/wk.worksheet.sc
index 8a1e237..3df4066 100644
--- a/src/main/scala/functorcoder/llm/wk.worksheet.sc
+++ b/src/main/scala/functorcoder/llm/wk.worksheet.sc
@@ -2,6 +2,7 @@ import functorcoder.llm.llmPrompt
import functorcoder.llm.llmPrompt.Prompt
import scala.collection.mutable.ArrayBuffer
import functorcoder.algo.treeParse
+import functorcoder.actions.createFiles.*
val Modification = llmPrompt
.Modification(code = "val x = 1", taskRequirement = "add documentation")
@@ -28,3 +29,5 @@ TreeNode("root", ArrayBuffer())
val input = "(root [(folder1 [(file1 file2) folder2]) folder3])"
val tree = treeParse.parse(input)
+
+tree2list(tree.get)
diff --git a/src/main/scala/vscextension/quickPick.scala b/src/main/scala/vscextension/quickPick.scala
index c0074d3..53ffc85 100644
--- a/src/main/scala/vscextension/quickPick.scala
+++ b/src/main/scala/vscextension/quickPick.scala
@@ -40,14 +40,13 @@ object quickPick {
modifieF(quickPick)
quickPick.buttons = js.Array(vscode.QuickInputButtons.Back)
- quickPick.items = items.toJSArray.map { (itemStr, itemDesc, _) => //
+ quickPick.items = items.map { (itemStr, itemDesc, _) => //
vscode
.QuickPickItem(itemStr)
.setAlwaysShow(true)
.setButtons(js.Array(vscode.QuickInputButtons.Back))
- .setDescription(itemStr)
.setDetail(itemDesc)
- }
+ }.toJSArray
quickPick.onDidChangeSelection { selection =>
println(s"selected: ${selection(0).label}")
From f61889f97b0743d9ace1aad2a4a2920ce48e9415 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 11 Apr 2025 18:02:45 +0200
Subject: [PATCH 32/46] Enhance LLM integration by adding model specification
and refactoring code completion logic; update README with upcoming features
---
README.md | 6 ++++
.../functorcoder/actions/CodeCompletion.scala | 31 ++++++++++++++++
.../scala/functorcoder/actions/Commands.scala | 2 --
.../functorcoder/actions/createFiles.scala | 9 ++---
.../functorcoder/editorUI/editorConfig.scala | 7 +++-
src/main/scala/functorcoder/llm/llmMain.scala | 8 ++---
.../scala/functorcoder/llm/llmPrompt.scala | 35 ++++---------------
.../vscextension/inlineCompletions.scala | 13 +++----
src/main/scala/vscextension/settings.scala | 3 +-
9 files changed, 64 insertions(+), 50 deletions(-)
diff --git a/README.md b/README.md
index 013e312..6982ebf 100644
--- a/README.md
+++ b/README.md
@@ -176,6 +176,12 @@ vscode.commands.registerCommand(name, fun).asInstanceOf[Dispose]
You can find more information and tutorials on the [Scala.js website](https://www.scala-js.org/).
+# feedback
+features to be implemented:
+- refactoring
+- specify which LLM to use
+
+
# references:
- updated from [vscode-scalajs-hello](https://github.com/pme123/vscode-scalajs-hello) with Scala 3.3.3 and sbt.version=1.9.7.
- [VSCode Extension Samples](https://github.com/microsoft/vscode-extension-samples) repository.
diff --git a/src/main/scala/functorcoder/actions/CodeCompletion.scala b/src/main/scala/functorcoder/actions/CodeCompletion.scala
index fe4de50..9670572 100644
--- a/src/main/scala/functorcoder/actions/CodeCompletion.scala
+++ b/src/main/scala/functorcoder/actions/CodeCompletion.scala
@@ -1 +1,32 @@
package functorcoder.actions
+
+import functorcoder.llm.llmMain.llmAgent
+import functorcoder.llm.llmPrompt
+import scala.concurrent.Future
+
+object CodeCompletion {
+
+ /** Generates a code completion suggestion by sending a prompt to a language model.
+ *
+ * @param codeBefore
+ * The code snippet preceding the hole where completion is required.
+ * @param codeAfter
+ * The code snippet following the hole where completion is required.
+ * @param llm
+ * The language model agent used to generate the completion.
+ * @return
+ * A `Future` containing the generated code completion as a `String`.
+ */
+ def getCompletion(
+ codeBefore: String, // code before the hole
+ codeAfter: String, // code after the hole
+ llm: llmAgent
+ ): Future[String] = {
+
+ val prompt = llmPrompt
+ .Completion(codeWithHole = s"$codeBefore${llmPrompt.promptText.hole}$codeAfter")
+
+ // assistantMessage: String = promptText.prompt1
+ llm.sendPrompt(prompt)
+ }
+}
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index b283b29..c48f5e4 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -46,7 +46,6 @@ object Commands {
statusBar.showSpininngStatusBarItem("functorcoder", llmResponse)
llmResponse.foreach { response =>
- // showMessageAndLog("add doc: " + s"${param.documentUri}, ${param.range}, ${response}")
// apply the changes to the document
vscode.window.activeTextEditor.toOption match {
case None =>
@@ -132,6 +131,5 @@ object Commands {
}
)
}
-
}
}
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
index 7b4343f..086a2e2 100644
--- a/src/main/scala/functorcoder/actions/createFiles.scala
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -3,6 +3,7 @@ package functorcoder.actions
import com.doofin.stdScala.dataTypes.Tree.TreeNode
import functorcoder.algo.treeParse
import vscextension.facade.vscodeUtils.showMessageAndLog
+import pprint.PPrinter.BlackWhite
/** create files and folders according to the prompt
*/
@@ -49,13 +50,13 @@ object createFiles {
*/
def createFilesAndFolders(tree: TreeNode[String], parentPath0: String): Unit = {
// recursively create files and folders
- showMessageAndLog(s"Files and folders tree: $tree")
+ val treeStr = BlackWhite.tokenize(tree).map(_.render).mkString("\n")
+ showMessageAndLog(s"Files and folders tree: $treeStr")
val TreeNode(root, children) = tree
val parentPath: String = parentPath0 + "/" + root
+ showMessageAndLog(s"Creating file in $parentPath, file: $root")
- children.foreach { child =>
- val file = child.value
- showMessageAndLog(s"Creating file in $parentPath, file: $file")
+ children.toSeq.foreach { child =>
createFilesAndFolders(child, parentPath)
}
}
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index 00505e9..a65da06 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -2,6 +2,11 @@ package functorcoder.editorUI
// https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
object editorConfig {
- case class Config(openaiApiKey: String, openaiUrl: String, maxTokens: Int)
+ case class Config(
+ openaiApiKey: String, //
+ openaiUrl: String,
+ maxTokens: Int,
+ model: String = "gpt-4o-mini"
+ )
}
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index 726e535..391a6fe 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -13,13 +13,11 @@ import scala.concurrent.Future
import functorcoder.editorUI.editorConfig
-/** large language model (LLM) AI main
- *
- * use node-fetch for network requests
+/** large language model (LLM) main entry
*/
object llmMain {
- /** generate a completion prompt
+ /** prompt data to string
*
* change the model here if needed
*
@@ -37,7 +35,7 @@ object llmMain {
openaiReq.Message(roles.user, inputPrompt.generatePrompt),
openaiReq.Message(roles.system, inputPrompt.getAssistantMessage)
),
- openaiReq.models.gpt4oMini,
+ editorCfg.model,
max_tokens = Some(editorCfg.maxTokens)
)
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 39a5458..fc7b9d9 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -51,7 +51,7 @@ object llmPrompt {
case class Completion(
codeWithHole: String, // code with a hole to fill like {{FILL_HERE}}
// taskRequirement: String, // like "Fill the {{FILL_HERE}} hole."
- assistantMessage: String = promptText.prompt1
+ assistantMessage: String = promptText.promptComp1
) extends Prompt(assistantMessage) {
def generatePrompt = {
@@ -87,14 +87,14 @@ object llmPrompt {
case class CreateFiles(
userRequest: String,
assistantMessage: String =
- s"You are given a user requirement wrapped in ${tagsInUse.queryStart} and ${tagsInUse.queryEnd}, and a TASK requirement ${tagsInUse.task}. " +
- "You are going to return the code snippet according to the TASK requirement. "
+ s"an input is wrapped in ${tagsInUse.queryStart} and ${tagsInUse.queryEnd}, and the requirement is inside ${tagsInUse.task}. " +
+ "from input and requirement, You return the code snippet"
) extends Prompt(assistantMessage) {
def generatePrompt = {
import functorcoder.algo.treeParse
val task =
- s"parse the prompt response to tree of files and folders in the format: ${treeParse.exampleSyntax}. An example input is: ${treeParse.exampleInput}. return the tree data structure in that format."
+ s" return tree of files and folders in the format: ${treeParse.exampleSyntax}. An example input is: ${treeParse.exampleInput}. return the tree data structure in that format."
s"""${tagsInUse.queryStart}
|${userRequest}
@@ -110,12 +110,12 @@ object llmPrompt {
*/
object promptText {
val hole = "{{FILL_HERE}}"
- val prompt1 =
+ val promptComp1 =
"You are a code or text autocompletion assistant. " +
s"In the provided input, missing code or text are marked as $hole. " +
"Your task is to output only the snippet that replace the placeholder, " +
"ensuring that indentation and formatting remain consistent with the context. Don't quote your output"
- val prompt2 =
+ val promptComp2 =
"You are a hole filler," +
"You are given a string with a hole: " +
s"$hole in the string, " +
@@ -144,27 +144,4 @@ function sum_evens(lim) {
TASK: Fill the {{FILL_HERE}} hole.
-
-## CORRECT COMPLETION
-
-if (i % 2 === 0) {
- sum += i;
- }
-
-## EXAMPLE QUERY:
-
-
-def sum_list(lst):
- total = 0
- for x in lst:
- {{FILL_HERE}}
- return total
-
-print sum_list([1, 2, 3])
-
-
-## CORRECT COMPLETION:
-
- total += x
-
*/
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index d4e3262..b25d650 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -5,10 +5,10 @@ import scala.concurrent.ExecutionContext.Implicits.global
import scala.scalajs.js
import scala.scalajs.js.JSConverters.*
-
import scala.scalajs.js.Promise
-import functorcoder.llm.llmPrompt
+
import functorcoder.llm.llmMain.llmAgent
+import functorcoder.actions.CodeCompletion
import vscextension.facade.vscodeUtils.showMessageAndLog
/** demonstrates how to provide inline completions in the editor. like the github copilot
@@ -29,11 +29,7 @@ object inlineCompletions {
val codeBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position))
val codeAfter = document.getText(new vscode.Range(position, document.positionAt(document.getText().length)))
- val prompt = llmPrompt
- .Completion(codeWithHole = s"$codeBefore${llmPrompt.promptText.hole}$codeAfter")
-
- // assistantMessage: String = promptText.prompt1
- val promptResponseF = llm.sendPrompt(prompt)
+ val promptResponseF = CodeCompletion.getCompletion(codeBefore, codeAfter, llm)
val providerResultF: Promise[scala.scalajs.js.Array[vscode.InlineCompletionItem]] =
promptResponseF.map { completionText =>
@@ -46,7 +42,8 @@ object inlineCompletions {
)
}.toJSPromise
- statusBar.showSpininngStatusBarItem("functorcoder", providerResultF)
+ statusBar.showSpininngStatusBarItem(s"functorcoder(${editorAPI.getLanguage()})", providerResultF)
+
providerResultF.asInstanceOf[typings.vscode.mod.ProviderResult[
scala.scalajs.js.Array[typings.vscode.mod.InlineCompletionItem] | typings.vscode.mod.InlineCompletionList
]]
diff --git a/src/main/scala/vscextension/settings.scala b/src/main/scala/vscextension/settings.scala
index cc97426..982e0d8 100644
--- a/src/main/scala/vscextension/settings.scala
+++ b/src/main/scala/vscextension/settings.scala
@@ -18,7 +18,8 @@ object settings {
config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
val maxTokens = config.get[Int]("maxTokens").getOrElse(1000)
- Config(openaiApiKey, openaiUrl, maxTokens)
+ val model = config.getStringOrEmpty("model", default = "gpt-4o-mini")
+ Config(openaiApiKey, openaiUrl, maxTokens, model)
}
extension (config: vscode.WorkspaceConfiguration) {
From 68906938fc472af2613ca9fa90b4341196ec3b51 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 11 Apr 2025 19:15:16 +0200
Subject: [PATCH 33/46] Refactor configuration handling by replacing settings
with vscConfig; update model default to use openaiReq; add .worksheet.sc to
.gitignore
---
.gitignore | 1 +
.../functorcoder/editorUI/editorConfig.scala | 4 ++-
src/main/scala/functorcoder/llm/llmMain.scala | 15 ++++++-----
src/main/scala/vscextension/CodeActions.scala | 25 ++++++++++++++-----
.../scala/vscextension/extensionMain.scala | 2 +-
.../{settings.scala => vscConfig.scala} | 7 ++++--
6 files changed, 38 insertions(+), 16 deletions(-)
rename src/main/scala/vscextension/{settings.scala => vscConfig.scala} (94%)
diff --git a/.gitignore b/.gitignore
index 1257c9f..71c82be 100644
--- a/.gitignore
+++ b/.gitignore
@@ -38,3 +38,4 @@ out
/project/target/
/.bsp/
+*.worksheet.sc
diff --git a/src/main/scala/functorcoder/editorUI/editorConfig.scala b/src/main/scala/functorcoder/editorUI/editorConfig.scala
index a65da06..443772e 100644
--- a/src/main/scala/functorcoder/editorUI/editorConfig.scala
+++ b/src/main/scala/functorcoder/editorUI/editorConfig.scala
@@ -1,12 +1,14 @@
package functorcoder.editorUI
+import functorcoder.llm.openaiReq
+
// https://code.visualstudio.com/api/references/contribution-points#contributes.configuration
object editorConfig {
case class Config(
openaiApiKey: String, //
openaiUrl: String,
maxTokens: Int,
- model: String = "gpt-4o-mini"
+ model: String = openaiReq.models.gpt4o
)
}
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index 391a6fe..d6d61f8 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -1,16 +1,15 @@
package functorcoder.llm
-import openaiReq.*
-import typings.nodeFetch.mod as nodeFetch
-
import scala.scalajs.concurrent.JSExecutionContext.Implicits.queue
import scala.scalajs.js
-import vscextension.facade.vscodeUtils.*
-
import scala.scalajs.js.Thenable.Implicits.*
import scala.concurrent.Future
+import typings.nodeFetch.mod as nodeFetch
+
+import vscextension.facade.vscodeUtils.*
+import openaiReq.*
import functorcoder.editorUI.editorConfig
/** large language model (LLM) main entry
@@ -27,7 +26,6 @@ object llmMain {
*/
def prompt2str(editorCfg: editorConfig.Config, inputPrompt: llmPrompt.Prompt) = {
// showMessageAndLog(s"prompt: ${inputPrompt}")
- // showMessageAndLog(s"prompt assistant: ${inputPrompt.getAssistantMessage}")
val openAiRequest = openaiReq
.OpenAiRequest(
@@ -43,6 +41,11 @@ object llmMain {
openAiRequest.toJson
}
+ /** llm agent to send request to openai api
+ *
+ * @param editorCfg
+ * the editor configuration
+ */
case class llmAgent(editorCfg: editorConfig.Config) {
val url = editorCfg.openaiUrl
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index ab86523..074db63 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -2,6 +2,7 @@ package vscextension
import scala.scalajs.js
import scala.concurrent.Future
+import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
@@ -9,6 +10,8 @@ import facade.vscodeUtils.*
import functorcoder.llm.llmMain.llmAgent
import functorcoder.llm.llmPrompt
import functorcoder.types.editorCtx.*
+import functorcoder.actions.Commands
+import cats.syntax.show
/** Code actions are commands provided at the cursor in the editor, so users can
*
@@ -47,18 +50,28 @@ object CodeActions {
kind = vscode.CodeActionKind.QuickFix
) {
isPreferred = true // show it first
- val args: codeActionParam[Future[String]] = new codeActionParam(
- document.uri.toString(),
- range,
- llmResponse
- )
+
+ // there are no onSelect events for code actions
+ // so we need to create a command and set it here
+ // edit = new vscode.WorkspaceEdit() {
+ // showMessageAndLog("creating edit") // triggered immediately
+ // }
// invoke command
+
command = vscode
.Command(
command = functorcoder.actions.Commands.cmdAddDocs._1, //
title = "add documentation" //
)
- .setArguments(js.Array(args))
+ .setArguments(
+ js.Array(
+ new codeActionParam(
+ document.uri.toString(),
+ range,
+ llmResponse
+ )
+ )
+ )
}
// can return array or promise of array
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index d498c65..d50f7fe 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -16,7 +16,7 @@ object extensionMain {
// showMessageAndLog("congrats, your scala.js vscode extension is loaded")
// vscode.workspace.rootPath.getOrElse("")
- val cfg = settings.readConfig()
+ val cfg = vscConfig.readConfig()
val llm = functorcoder.llm.llmMain.llmAgent(cfg)
// showMessageAndLog(s"config loaded: ${cfg.toString()}")
diff --git a/src/main/scala/vscextension/settings.scala b/src/main/scala/vscextension/vscConfig.scala
similarity index 94%
rename from src/main/scala/vscextension/settings.scala
rename to src/main/scala/vscextension/vscConfig.scala
index 982e0d8..744d780 100644
--- a/src/main/scala/vscextension/settings.scala
+++ b/src/main/scala/vscextension/vscConfig.scala
@@ -3,7 +3,7 @@ package vscextension
import typings.vscode.mod as vscode
import functorcoder.editorUI.editorConfig.Config
-object settings {
+object vscConfig {
/** read the configuration from the vscode settings.json
*
@@ -11,6 +11,8 @@ object settings {
*/
def readConfig() = {
val config = vscode.workspace.getConfiguration("functorcoder")
+
+ // get the key values from vscode settings json
val openaiApiKey =
config.getStringOrEmpty("openaiApiKey")
@@ -18,7 +20,8 @@ object settings {
config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
val maxTokens = config.get[Int]("maxTokens").getOrElse(1000)
- val model = config.getStringOrEmpty("model", default = "gpt-4o-mini")
+ val model = config.getStringOrEmpty("model", default = "gpt-4o")
+
Config(openaiApiKey, openaiUrl, maxTokens, model)
}
From fe08ca47a6511efbb45b27472ec9cec91b8abc92 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 11 Apr 2025 19:47:14 +0200
Subject: [PATCH 34/46] Refactor code completion and documentation handling;
remove obsolete CodeCompletion and editorCtx files, and introduce CodeGen for
improved functionality
---
.../{CodeCompletion.scala => CodeGen.scala} | 22 ++++++++++-
.../scala/functorcoder/actions/Commands.scala | 2 +-
.../scala/functorcoder/llm/llmPrompt.scala | 8 ++--
.../scala/functorcoder/types/editorCtx.scala | 20 ----------
.../functorcoder/types/editorTypes.scala | 39 +++++++++++++++++++
src/main/scala/vscextension/CodeActions.scala | 29 +++++++-------
.../vscextension/inlineCompletions.scala | 4 +-
7 files changed, 81 insertions(+), 43 deletions(-)
rename src/main/scala/functorcoder/actions/{CodeCompletion.scala => CodeGen.scala} (67%)
delete mode 100644 src/main/scala/functorcoder/types/editorCtx.scala
create mode 100644 src/main/scala/functorcoder/types/editorTypes.scala
diff --git a/src/main/scala/functorcoder/actions/CodeCompletion.scala b/src/main/scala/functorcoder/actions/CodeGen.scala
similarity index 67%
rename from src/main/scala/functorcoder/actions/CodeCompletion.scala
rename to src/main/scala/functorcoder/actions/CodeGen.scala
index 9670572..848b576 100644
--- a/src/main/scala/functorcoder/actions/CodeCompletion.scala
+++ b/src/main/scala/functorcoder/actions/CodeGen.scala
@@ -3,8 +3,9 @@ package functorcoder.actions
import functorcoder.llm.llmMain.llmAgent
import functorcoder.llm.llmPrompt
import scala.concurrent.Future
+import vscextension.editorAPI
-object CodeCompletion {
+object CodeGen {
/** Generates a code completion suggestion by sending a prompt to a language model.
*
@@ -29,4 +30,23 @@ object CodeCompletion {
// assistantMessage: String = promptText.prompt1
llm.sendPrompt(prompt)
}
+
+ def getDocumentation(
+ selectedCode: String,
+ llm: llmAgent
+ ) = {
+ val language = editorAPI.getLanguage()
+ val llmResponse =
+ llm.sendPrompt(
+ llmPrompt.Modification(
+ code = selectedCode, //
+ taskRequirement = llmPrompt.generateDocs(language)
+ )
+ )
+
+ val commandName = functorcoder.actions.Commands.cmdAddDocs._1
+
+ (llmResponse, commandName)
+ }
+
}
diff --git a/src/main/scala/functorcoder/actions/Commands.scala b/src/main/scala/functorcoder/actions/Commands.scala
index c48f5e4..66ba8f1 100644
--- a/src/main/scala/functorcoder/actions/Commands.scala
+++ b/src/main/scala/functorcoder/actions/Commands.scala
@@ -10,7 +10,7 @@ import typings.vscode.mod as vscode
import vscextension.quickPick
import vscextension.facade.vscodeUtils.showMessageAndLog
-import functorcoder.types.editorCtx.codeActionParam
+import functorcoder.types.editorTypes.codeActionParam
import functorcoder.llm.llmMain.llmAgent
import functorcoder.algo.treeParse
import vscextension.statusBar
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index fc7b9d9..224681b 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -125,10 +125,10 @@ object llmPrompt {
}
def generateDocs(language: String) = {
- "generate short documentation for the input code, " +
- "and return only the documentation for language: " + language +
- "the documentation shall be the format according to the language, " +
- "but don't wrap it with backticks or any other tags."
+ s"generate short documentation for the input code in language: $language." +
+ "return only the documentation, " +
+ "the documentation conform to the format according to the language." +
+ "Don't wrap it with backticks or any other tags."
}
}
diff --git a/src/main/scala/functorcoder/types/editorCtx.scala b/src/main/scala/functorcoder/types/editorCtx.scala
deleted file mode 100644
index a97b253..0000000
--- a/src/main/scala/functorcoder/types/editorCtx.scala
+++ /dev/null
@@ -1,20 +0,0 @@
-package functorcoder.types
-import scala.scalajs.js
-
-object editorCtx {
-
- /** the context of the editor
- *
- * @param language
- * the programming language of the file
- */
- case class EditorContext(
- language: String
- )
-
- class codeActionParam[T](
- val documentUri: String, //
- val range: typings.vscode.mod.Selection,
- val param: T
- ) extends js.Object
-}
diff --git a/src/main/scala/functorcoder/types/editorTypes.scala b/src/main/scala/functorcoder/types/editorTypes.scala
new file mode 100644
index 0000000..6482839
--- /dev/null
+++ b/src/main/scala/functorcoder/types/editorTypes.scala
@@ -0,0 +1,39 @@
+package functorcoder.types
+import scala.scalajs.js
+
+object editorTypes {
+
+ /** the context of the editor
+ *
+ * @param language
+ * the programming language of the file
+ */
+ case class EditorContext(
+ language: String
+ )
+
+ class codeActionParam[T](
+ val documentUri: String, //
+ val range: typings.vscode.mod.Selection,
+ val param: T
+ ) extends js.Object
+
+ /* .Command(
+ command = functorcoder.actions.Commands.cmdAddDocs._1, //
+ title = "add documentation" //
+ )
+ .setArguments(
+ js.Array(
+ new codeActionParam(
+ document.uri.toString(),
+ range,
+ llmResponse
+ )
+ )
+ ) */
+ case class commandData[Param](
+ commandName: String,
+ title: String,
+ arguments: Param
+ )
+}
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index 074db63..cae8886 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -9,9 +9,10 @@ import typings.vscode.mod as vscode
import facade.vscodeUtils.*
import functorcoder.llm.llmMain.llmAgent
import functorcoder.llm.llmPrompt
-import functorcoder.types.editorCtx.*
+import functorcoder.types.editorTypes.*
import functorcoder.actions.Commands
import cats.syntax.show
+import functorcoder.actions.CodeGen
/** Code actions are commands provided at the cursor in the editor, so users can
*
@@ -33,23 +34,21 @@ object CodeActions {
context: vscode.CodeActionContext
) = {
val selectedCode = document.getText(range)
- val language = editorAPI.getLanguage()
- val llmResponse =
- llm.sendPrompt(
- llmPrompt.Modification(
- code = selectedCode, //
- taskRequirement = llmPrompt.generateDocs(language)
- )
- )
-
- // show the spinner when waiting
- statusBar.showSpininngStatusBarItem(s"functorcoder($language)", llmResponse)
- val fix1 =
+
+ val addDocsItem =
new vscode.CodeAction(
title = "add documentation for selected code",
kind = vscode.CodeActionKind.QuickFix
) {
isPreferred = true // show it first
+ val language = editorAPI.getLanguage()
+ val (llmResponse, commandName) =
+ CodeGen.getDocumentation(
+ selectedCode,
+ llm
+ )
+
+ statusBar.showSpininngStatusBarItem(s"functorcoder($language)", llmResponse)
// there are no onSelect events for code actions
// so we need to create a command and set it here
@@ -60,7 +59,7 @@ object CodeActions {
command = vscode
.Command(
- command = functorcoder.actions.Commands.cmdAddDocs._1, //
+ command = commandName, //
title = "add documentation" //
)
.setArguments(
@@ -76,7 +75,7 @@ object CodeActions {
}
// can return array or promise of array
- js.Array(fix1)
+ js.Array(addDocsItem)
}
def provideCodeActions(
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index b25d650..fde345e 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -8,7 +8,7 @@ import scala.scalajs.js.JSConverters.*
import scala.scalajs.js.Promise
import functorcoder.llm.llmMain.llmAgent
-import functorcoder.actions.CodeCompletion
+import functorcoder.actions.CodeGen
import vscextension.facade.vscodeUtils.showMessageAndLog
/** demonstrates how to provide inline completions in the editor. like the github copilot
@@ -29,7 +29,7 @@ object inlineCompletions {
val codeBefore = document.getText(new vscode.Range(new vscode.Position(0, 0), position))
val codeAfter = document.getText(new vscode.Range(position, document.positionAt(document.getText().length)))
- val promptResponseF = CodeCompletion.getCompletion(codeBefore, codeAfter, llm)
+ val promptResponseF = CodeGen.getCompletion(codeBefore, codeAfter, llm)
val providerResultF: Promise[scala.scalajs.js.Array[vscode.InlineCompletionItem]] =
promptResponseF.map { completionText =>
From 7b2ba2b6a239507ae66496ae850acbb8136d3792 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 11 Apr 2025 19:47:38 +0200
Subject: [PATCH 35/46] Remove unused imports and clean up code in editorUI and
extensionMain
---
src/main/scala/functorcoder/editorUI/menu.scala | 2 --
src/main/scala/vscextension/CodeActions.scala | 4 ----
src/main/scala/vscextension/extensionMain.scala | 1 -
3 files changed, 7 deletions(-)
diff --git a/src/main/scala/functorcoder/editorUI/menu.scala b/src/main/scala/functorcoder/editorUI/menu.scala
index 4d7d0ff..385ea1a 100644
--- a/src/main/scala/functorcoder/editorUI/menu.scala
+++ b/src/main/scala/functorcoder/editorUI/menu.scala
@@ -1,8 +1,6 @@
package functorcoder.editorUI
-import typings.vscode.mod as vscode
import vscextension.facade.vscodeUtils.*
-import vscextension.quickPick
import functorcoder.llm.llmMain.llmAgent
import functorcoder.actions.Commands
diff --git a/src/main/scala/vscextension/CodeActions.scala b/src/main/scala/vscextension/CodeActions.scala
index cae8886..d69108b 100644
--- a/src/main/scala/vscextension/CodeActions.scala
+++ b/src/main/scala/vscextension/CodeActions.scala
@@ -2,16 +2,12 @@ package vscextension
import scala.scalajs.js
import scala.concurrent.Future
-import scala.concurrent.ExecutionContext.Implicits.global
import typings.vscode.mod as vscode
import facade.vscodeUtils.*
import functorcoder.llm.llmMain.llmAgent
-import functorcoder.llm.llmPrompt
import functorcoder.types.editorTypes.*
-import functorcoder.actions.Commands
-import cats.syntax.show
import functorcoder.actions.CodeGen
/** Code actions are commands provided at the cursor in the editor, so users can
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index d50f7fe..8c66ddf 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -5,7 +5,6 @@ import scala.scalajs.js.annotation.JSExportTopLevel
import typings.vscode.mod as vscode
-import facade.vscodeUtils.*
object extensionMain {
From 657a968843ea095d28b98903aefdabe3ebabb814 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 15 Apr 2025 14:44:02 +0200
Subject: [PATCH 36/46] Add .sbtopts to .gitignore to exclude build options
from version control
---
.gitignore | 1 +
1 file changed, 1 insertion(+)
diff --git a/.gitignore b/.gitignore
index 71c82be..36a6d9a 100644
--- a/.gitignore
+++ b/.gitignore
@@ -39,3 +39,4 @@ out
/.bsp/
*.worksheet.sc
+.sbtopts
\ No newline at end of file
From 6b3678280a2debaac6b36a1d4210a5bc165b04ec Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 15 Apr 2025 14:44:49 +0200
Subject: [PATCH 37/46] Update README to include RAG feature for enhanced code
base understanding
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 6982ebf..6f2492f 100644
--- a/README.md
+++ b/README.md
@@ -180,6 +180,7 @@ You can find more information and tutorials on the [Scala.js website](https://ww
features to be implemented:
- refactoring
- specify which LLM to use
+- RAG(retrieval-augmented generation) to understand the whole code base
# references:
From f307bd5592e748141d017757f19dd3422626ba9d Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Tue, 15 Apr 2025 14:49:26 +0200
Subject: [PATCH 38/46] Update README to include MCP feature for environment
interaction
---
README.md | 1 +
1 file changed, 1 insertion(+)
diff --git a/README.md b/README.md
index 6f2492f..a5609a7 100644
--- a/README.md
+++ b/README.md
@@ -181,6 +181,7 @@ features to be implemented:
- refactoring
- specify which LLM to use
- RAG(retrieval-augmented generation) to understand the whole code base
+- MCP(model context protocol) to interact with the environment, like external tools, etc.
# references:
From 84cfe72c01f90ea66246185dcf671902c6227c3b Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Wed, 16 Apr 2025 17:05:09 +0200
Subject: [PATCH 39/46] Update README for clarity and structure; modify prompt
placeholders in llmPrompt; adjust logging in inlineCompletions; add comments
in createFiles
---
README.md | 168 ++++--------------
.../functorcoder/actions/createFiles.scala | 3 +
.../scala/functorcoder/llm/llmPrompt.scala | 12 +-
.../vscextension/inlineCompletions.scala | 2 +-
4 files changed, 41 insertions(+), 144 deletions(-)
diff --git a/README.md b/README.md
index 6982ebf..b002518 100644
--- a/README.md
+++ b/README.md
@@ -1,42 +1,32 @@
# functorcoder
-**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design.
+**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design in Scala.js. It's aimed at providing a clean and extensible architecture for AI coding assistants, which is helpful for understanding basic mechanics if you want to build your own AI coding assistant.
features aiming to implement:
- code generation: completion, documentation
- code modification: refactoring, optimization, bug fixing
- code analysis: code understanding, code review, code quality
-we think in mathematics, algebra and functional programming.
-
- Input = {Query, CodeSnippet, Spec}: The set of all possible input types (queries, code snippets, or requirements/specifications).
-
- Output = {Code, Explanation, Transformation, DebugSuggestion}: The set of all possible outputs.
-
-The types and objects for Input:
-- code snippet or code file: a piece of code
-- code context: a code snippet with its surrounding code
-- query: natural language query
-- specification: natural language specification
-
-The Output:
-- code snippet or code file: a piece of code, including completion, refactoring, optimization, bug fixing
-- explanation: a natural language explanation
-- transformation: the transformation of the input code
-- suggestion: a suggestion for debugging or improvement or refactoring
-
-## current status
-features implemented:
-- auto completion
+current features implemented:
+- auto completion as you type
- add documentation quick fix action
## Project Structure
-package name: com.functorcoder
-It is the core module of the AI coding assistant, including below features:
+The project is divided into two main parts: the core module and the VSCode extension module under /src/main/scala/functorcoder and /src/main/scala/vscextension respectively.
+
+The first part is the core module, containing the main logic of the AI coding assistant:
- Large Language Model (LLM) integration
- sending propmt to LLM and getting the response
+The second part is the VSCode extension module, which integrates the core module with the VSCode editor. It contains:
+- commands: commands to be executed in the editor
+- code actions: quick fix actions
+- code completion: auto completion
+- editor ui: status bar, notifications, etc.
+
+It's adopted from the [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) project. Refer to it for getting started with the VSCode extension development in Scala.js.
+
-project file structure:
+project file structure for the core module:
```bash
/functorcoder
├── /src/main/scala/functorcoder
@@ -59,8 +49,7 @@ project file structure:
└── API.md # API documentation for integration
```
-The vscode extension package will be in the `vscextension` folder, which is in charge of integrating the AI part with the VSCode editor. It's adopted from the [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) project.
-
+The project file structure for the VSCode extension module:
```bash
/vscextension
├── /src/main/scala/vscextension
@@ -70,121 +59,26 @@ The vscode extension package will be in the `vscextension` folder, which is in c
...
```
+## design principles
+I am to design the system with mathematics, algebra and functional programming principles in mind. The system is designed to be modular and extensible, allowing for easy addition of new features and components.
-The bash commands to create above structure(folders and files) are:
-```bash
-mkdir -p src/main/scala/functorcoder/types
-
-touch src/main/scala/functorcoder/types/InputTypes.scala
-```
-
-### Setup
-Requirements:
- - [Sbt](https://www.scala-sbt.org/download.html)
-
-
-Run the vscode extension:
-* Clone this project
-* Open the project in VSCode, run the `import build` task with Metals (it should display a popup automatically).
-
-* run below command, which will open a new VSCode window with the extension loaded(first time it will take some time for scalable typed to convert typescript to scala.js):
-```bash
-sbt open
-```
-
-After the new VSCode (extension development host) window opens:
-* Run the Hello World command from the Command Palette (`⇧⌘P`) in the new VSCode window.
-* Type `hello` and select `Hello World`.
- * You should see a Notification _Hello World!_.
-
-
-### Use it as a template
-click on the `Use this template` button to create a new repository with the same structure in github.
-
-### Use it as sbt dependency
-In your `build.sbt` add the following:
-```scala
-lazy val vsc = RootProject(uri("https://github.com/doofin/vscode-scalajs-hello.git"))
-lazy val root = Project("root", file(".")) dependsOn(vsc)
-```
-
-### Use it as a library
-**Currently not working** due to jitpack missing npm! Welcome to contribute to fix it.
-
-You can use this project as a library in your project by adding the following to your `build.sbt`:
-```scala
-resolvers += Resolver.bintrayRepo("jitpack", "https://jitpack.io")
-libraryDependencies += "com.github.doofin" % "vscode-scalajs-hello" % "master-SNAPSHOT" // might be wrong
-```
-
-You can find the latest version on
-[jitpack.](https://jitpack.io/#doofin/vscode-scalajs-hello)
-
-Note:
- - I recommend using the Metals extension for Scala in VSCode.
- - If you have any issues, please open an issue on this repository.
-
-## Project structure
-The project file structure in src/main/scala is as follows:
-```bash
-src/main/scala
-├── extensionMain.scala // main entry point for the extension
-├── commands.scala, codeActions.scala,etc // files for different extension features
-│ ├── facade // facade for vscode api
-│ ├── io // file and network io functions
-```
-
-
-The project uses the following tools:
-* [SBT] build tool for building the project
-* [Scala.js] for general coding
-* [Scalably Typed] for JavaScript facades
-* [scalajs-bundler] for bundling the JavaScript dependencies
-
-SBT is configured with the `build.sbt` file. Scala.js, ScalablyTyped and the bundler are SBT plugins. With these, SBT manages your JavaScript `npm` dependencies. You should never have to run `npm` directly, simply edit the `npmDependencies` settings in `build.sbt`.
-
-[accessible-scala]: https://marketplace.visualstudio.com/items?itemName=scala-center.accessible-scala
-[helloworld-minimal-sample]: https://github.com/Microsoft/vscode-extension-samples/tree/master/helloworld-minimal-sample
-[Scalably Typed]: https://github.com/ScalablyTyped/Converter
-[SBT]: https://www.scala-sbt.org
-[ScalaJS]: http://www.scala-js.org
-[scalajs-bundler]: https://github.com/scalacenter/scalajs-bundler
-
-## How to code in Scala js?
-
-In general, javascript functions and classes can be used in the same way as in JS/TS!
-If the typechecker disagrees, you can insert casts with `.asInstanceOf[Type]`.
-
-The JS types (like `js.Array`) are available from
-```scala
-import scala.scalajs.js
-```
-
-The VSCode classes and functions are available from
-```scala
-import typings.vscode.mod as vscode
+ Input = {Query, CodeSnippet, Spec}: The set of all possible input types (queries, code snippets, or requirements/specifications).
-vscode.window.showInformationMessage("Hello World!")
-```
+ Output = {Code, Explanation, Transformation, DebugSuggestion}: The set of all possible outputs.
-Some additional types are available in the `anon` subpackage, for example:
-```scala
-import typings.vscode.anon.Dispose
-// register a command. The cast is necessary due to typescript conversion limitations.
-vscode.commands.registerCommand(name, fun).asInstanceOf[Dispose]
-```
+The types and objects for Input:
+- code snippet or code file: a piece of code
+- code context: a code snippet with its surrounding code
+- query: natural language query
+- specification: natural language specification
-You can find more information and tutorials on the [Scala.js website](https://www.scala-js.org/).
+The Output:
+- code snippet or code file: a piece of code, including completion, refactoring, optimization, bug fixing
+- explanation: a natural language explanation
+- transformation: the transformation of the input code
+- suggestion: a suggestion for debugging or improvement or refactoring
# feedback
features to be implemented:
- refactoring
-- specify which LLM to use
-
-
-# references:
- - updated from [vscode-scalajs-hello](https://github.com/pme123/vscode-scalajs-hello) with Scala 3.3.3 and sbt.version=1.9.7.
- - [VSCode Extension Samples](https://github.com/microsoft/vscode-extension-samples) repository.
- - [visualstudio.com/api/get-started](https://code.visualstudio.com/api/get-started/your-first-extension) in typescript.
- - [scalablytyped.com](https://scalablytyped.org/docs/plugin) for the typing plugin.
- - [scala js](https://www.scala-js.org/doc/project/) for the scala.js project.
+- specify which LLM to use
\ No newline at end of file
diff --git a/src/main/scala/functorcoder/actions/createFiles.scala b/src/main/scala/functorcoder/actions/createFiles.scala
index 086a2e2..da4e317 100644
--- a/src/main/scala/functorcoder/actions/createFiles.scala
+++ b/src/main/scala/functorcoder/actions/createFiles.scala
@@ -50,6 +50,9 @@ object createFiles {
*/
def createFilesAndFolders(tree: TreeNode[String], parentPath0: String): Unit = {
// recursively create files and folders
+ // mkdir -p src/main/scala/functorcoder/types
+ // touch src/main/scala/functorcoder/types/InputTypes.scala
+
val treeStr = BlackWhite.tokenize(tree).map(_.render).mkString("\n")
showMessageAndLog(s"Files and folders tree: $treeStr")
val TreeNode(root, children) = tree
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 224681b..e2dffa0 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -109,18 +109,18 @@ object llmPrompt {
* more like art than science. just try different prompts and see what works best
*/
object promptText {
- val hole = "{{FILL_HERE}}"
+ val hole = "{{HOLE}}"
val promptComp1 =
"You are a code or text autocompletion assistant. " +
s"In the provided input, missing code or text are marked as $hole. " +
"Your task is to output only the snippet that replace the placeholder, " +
"ensuring that indentation and formatting remain consistent with the context. Don't quote your output"
+
val promptComp2 =
- "You are a hole filler," +
- "You are given a string with a hole: " +
- s"$hole in the string, " +
- "your task is to replace this hole with your reply." +
- "you only return the string for the hole with indentation, without any quotes"
+ "You are a hole filler." +
+ "Given a string with a hole: " + s"$hole in the string, " +
+ "you replace this hole with your reply." +
+ "only return the string for the hole with indentation, without any quotes"
}
diff --git a/src/main/scala/vscextension/inlineCompletions.scala b/src/main/scala/vscextension/inlineCompletions.scala
index fde345e..debc11a 100644
--- a/src/main/scala/vscextension/inlineCompletions.scala
+++ b/src/main/scala/vscextension/inlineCompletions.scala
@@ -33,7 +33,7 @@ object inlineCompletions {
val providerResultF: Promise[scala.scalajs.js.Array[vscode.InlineCompletionItem]] =
promptResponseF.map { completionText =>
- showMessageAndLog(s"completionText: $completionText")
+ // showMessageAndLog(s"completionText: $completionText")
js.Array(
new vscode.InlineCompletionItem(
insertText = completionText, // text to insert
From fcdc8dd910d9a3a42015ff859461f6cf645d2f7f Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Fri, 18 Apr 2025 20:12:27 +0200
Subject: [PATCH 40/46] Update README for improved clarity and add getting
started section
---
README.md | 6 +++++-
1 file changed, 5 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 13265bc..63dd1c0 100644
--- a/README.md
+++ b/README.md
@@ -1,5 +1,5 @@
# functorcoder
-**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design in Scala.js. It's aimed at providing a clean and extensible architecture for AI coding assistants, which is helpful for understanding basic mechanics if you want to build your own AI coding assistant.
+**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design in Scala.js. It aims at providing a clean and extensible architecture for AI coding assistants, which is helpful for understanding basic mechanics if you want to build your own AI coding assistant.
features aiming to implement:
- code generation: completion, documentation
@@ -10,6 +10,9 @@ current features implemented:
- auto completion as you type
- add documentation quick fix action
+## Getting Started
+Visit [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) to understand how to play with scala.js for VSCode extension development. Basically, sbt is used to build the project and run the extension.
+
## Project Structure
The project is divided into two main parts: the core module and the VSCode extension module under /src/main/scala/functorcoder and /src/main/scala/vscextension respectively.
@@ -59,6 +62,7 @@ The project file structure for the VSCode extension module:
...
```
+
## design principles
I am to design the system with mathematics, algebra and functional programming principles in mind. The system is designed to be modular and extensible, allowing for easy addition of new features and components.
From 91f632711d926d7192d7cf22859766003e747e03 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sat, 19 Apr 2025 20:31:02 +0200
Subject: [PATCH 41/46] better prompt
---
src/main/scala/functorcoder/llm/llmMain.scala | 4 +--
.../scala/functorcoder/llm/llmPrompt.scala | 6 ++--
.../scala/functorcoder/llm/wk.worksheet.sc | 33 -------------------
3 files changed, 6 insertions(+), 37 deletions(-)
delete mode 100644 src/main/scala/functorcoder/llm/wk.worksheet.sc
diff --git a/src/main/scala/functorcoder/llm/llmMain.scala b/src/main/scala/functorcoder/llm/llmMain.scala
index d6d61f8..09eb5fe 100644
--- a/src/main/scala/functorcoder/llm/llmMain.scala
+++ b/src/main/scala/functorcoder/llm/llmMain.scala
@@ -30,8 +30,8 @@ object llmMain {
val openAiRequest = openaiReq
.OpenAiRequest(
List(
- openaiReq.Message(roles.user, inputPrompt.generatePrompt),
- openaiReq.Message(roles.system, inputPrompt.getAssistantMessage)
+ openaiReq.Message(roles.system, inputPrompt.getSysMessage),
+ openaiReq.Message(roles.user, inputPrompt.generatePrompt)
),
editorCfg.model,
max_tokens = Some(editorCfg.maxTokens)
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index e2dffa0..1e0bdc2 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -28,7 +28,7 @@ object llmPrompt {
// trait will have undefined value, so we use abstract class
sealed abstract class Prompt(val assistantMsg: String) {
def generatePrompt: String
- def getAssistantMessage: String = assistantMsg
+ def getSysMessage: String = assistantMsg
}
/** code completion prompt
@@ -51,7 +51,7 @@ object llmPrompt {
case class Completion(
codeWithHole: String, // code with a hole to fill like {{FILL_HERE}}
// taskRequirement: String, // like "Fill the {{FILL_HERE}} hole."
- assistantMessage: String = promptText.promptComp1
+ assistantMessage: String = promptText.promptComp3
) extends Prompt(assistantMessage) {
def generatePrompt = {
@@ -122,6 +122,8 @@ object llmPrompt {
"you replace this hole with your reply." +
"only return the string for the hole with indentation, without any quotes"
+ val promptComp3 = s"Fill in the missing text specified by $hole. Only return the string which replace the hole. " +
+ "Don't wrap it with backticks or any other tags"
}
def generateDocs(language: String) = {
diff --git a/src/main/scala/functorcoder/llm/wk.worksheet.sc b/src/main/scala/functorcoder/llm/wk.worksheet.sc
deleted file mode 100644
index 3df4066..0000000
--- a/src/main/scala/functorcoder/llm/wk.worksheet.sc
+++ /dev/null
@@ -1,33 +0,0 @@
-import functorcoder.llm.llmPrompt
-import functorcoder.llm.llmPrompt.Prompt
-import scala.collection.mutable.ArrayBuffer
-import functorcoder.algo.treeParse
-import functorcoder.actions.createFiles.*
-
-val Modification = llmPrompt
- .Modification(code = "val x = 1", taskRequirement = "add documentation")
-
-Modification.asInstanceOf[Prompt]
-
-sealed trait trait1(val param1: String) {
- // def method1: String = param1
-}
-
-case class class1(override val param1: String = "s1") extends trait1(param1)
-
-val c1 = class1()
-
-def m1(t1: trait1) = {
- println(s"t1 param1: ${t1.param1}")
-}
-// c1.method1
-
-import io.circe.generic.auto._
-import com.doofin.stdScalaCross.TreeNode
-
-TreeNode("root", ArrayBuffer())
-
-val input = "(root [(folder1 [(file1 file2) folder2]) folder3])"
-val tree = treeParse.parse(input)
-
-tree2list(tree.get)
From 456fc24acce45260d3047aba1a2f6f1fe3aba8ea Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sat, 19 Apr 2025 20:36:44 +0200
Subject: [PATCH 42/46] Refactor prompt class to use ctrlMsg instead of
assistantMsg; update configuration keys for API settings
---
src/main/scala/functorcoder/llm/llmPrompt.scala | 8 ++++----
src/main/scala/vscextension/vscConfig.scala | 10 +++++-----
2 files changed, 9 insertions(+), 9 deletions(-)
diff --git a/src/main/scala/functorcoder/llm/llmPrompt.scala b/src/main/scala/functorcoder/llm/llmPrompt.scala
index 1e0bdc2..72f629e 100644
--- a/src/main/scala/functorcoder/llm/llmPrompt.scala
+++ b/src/main/scala/functorcoder/llm/llmPrompt.scala
@@ -26,9 +26,9 @@ object llmPrompt {
)
// trait will have undefined value, so we use abstract class
- sealed abstract class Prompt(val assistantMsg: String) {
+ sealed abstract class Prompt(val ctrlMsg: String) {
def generatePrompt: String
- def getSysMessage: String = assistantMsg
+ def getSysMessage: String = ctrlMsg
}
/** code completion prompt
@@ -51,8 +51,8 @@ object llmPrompt {
case class Completion(
codeWithHole: String, // code with a hole to fill like {{FILL_HERE}}
// taskRequirement: String, // like "Fill the {{FILL_HERE}} hole."
- assistantMessage: String = promptText.promptComp3
- ) extends Prompt(assistantMessage) {
+ ctrlMessage: String = promptText.promptComp3
+ ) extends Prompt(ctrlMessage) {
def generatePrompt = {
codeWithHole
diff --git a/src/main/scala/vscextension/vscConfig.scala b/src/main/scala/vscextension/vscConfig.scala
index 744d780..2a283f5 100644
--- a/src/main/scala/vscextension/vscConfig.scala
+++ b/src/main/scala/vscextension/vscConfig.scala
@@ -13,16 +13,16 @@ object vscConfig {
val config = vscode.workspace.getConfiguration("functorcoder")
// get the key values from vscode settings json
- val openaiApiKey =
- config.getStringOrEmpty("openaiApiKey")
+ val apiKey =
+ config.getStringOrEmpty("apiKey")
- val openaiUrl =
- config.getStringOrEmpty(key = "openaiUrl", default = "https://api.openai.com/v1/chat/completions")
+ val apiEndpointUrl =
+ config.getStringOrEmpty(key = "apiUrl", default = "https://api.openai.com/v1/chat/completions")
val maxTokens = config.get[Int]("maxTokens").getOrElse(1000)
val model = config.getStringOrEmpty("model", default = "gpt-4o")
- Config(openaiApiKey, openaiUrl, maxTokens, model)
+ Config(apiKey, apiEndpointUrl, maxTokens, model)
}
extension (config: vscode.WorkspaceConfiguration) {
From d30fefe8976788846b2e3a92954da2e4ac2931fa Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 20 Apr 2025 17:01:21 +0200
Subject: [PATCH 43/46] Remove outdated OpenAI API request example from
openaiReq.scala and clean up comments
---
.../scala/functorcoder/llm/openaiReq.scala | 32 +++++++++----------
1 file changed, 16 insertions(+), 16 deletions(-)
diff --git a/src/main/scala/functorcoder/llm/openaiReq.scala b/src/main/scala/functorcoder/llm/openaiReq.scala
index a31fe7f..0139336 100644
--- a/src/main/scala/functorcoder/llm/openaiReq.scala
+++ b/src/main/scala/functorcoder/llm/openaiReq.scala
@@ -5,6 +5,22 @@ import io.circe.generic.auto._
import io.circe.*
import io.circe.syntax.*
+/* openAI API request
+https://platform.openai.com/docs/api-reference/chat
+'{
+ "model": "gpt-4o",
+ "messages": [
+ {
+ "role": "system",
+ "content": "You are a helpful assistant."
+ },
+ {
+ "role": "user",
+ "content": "Hello!"
+ }
+ ]
+ }'
+ */
object openaiReq {
/** https://platform.openai.com/docs/models/model-endpoint-compatibility
@@ -133,19 +149,3 @@ object openaiReq {
}
}
-/* openAI API request
-https://platform.openai.com/docs/api-reference/chat
-'{
- "model": "gpt-4o",
- "messages": [
- {
- "role": "system",
- "content": "You are a helpful assistant."
- },
- {
- "role": "user",
- "content": "Hello!"
- }
- ]
- }'
- */
From 25a7677956b4cb65606e427568fa2654eb2113e4 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 20 Apr 2025 18:29:48 +0200
Subject: [PATCH 44/46] Add OpenAI API configuration example to README and
clarify getting started instructions
---
README.md | 13 +++++++++++++
src/main/scala/vscextension/extensionMain.scala | 1 -
2 files changed, 13 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index 63dd1c0..93b68a3 100644
--- a/README.md
+++ b/README.md
@@ -13,9 +13,22 @@ current features implemented:
## Getting Started
Visit [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) to understand how to play with scala.js for VSCode extension development. Basically, sbt is used to build the project and run the extension.
+Before loading the extension, you need to add options to vscode user settings, and provide your OpenAI compatible API key and URL. Here is an example:
+
+```json
+"functorcoder": {
+ "apiKey": "somekey",
+ "apiUrl": "https://api.openai.com/v1/chat/completions",
+ "maxTokens": 512,
+ "model": "gpt-4o-mini",
+ }
+```
+
## Project Structure
The project is divided into two main parts: the core module and the VSCode extension module under /src/main/scala/functorcoder and /src/main/scala/vscextension respectively.
+**To get started**, read the file `extensionMain.scala` in the VSCode extension module. It is the main entry point for the extension.
+
The first part is the core module, containing the main logic of the AI coding assistant:
- Large Language Model (LLM) integration
- sending propmt to LLM and getting the response
diff --git a/src/main/scala/vscextension/extensionMain.scala b/src/main/scala/vscextension/extensionMain.scala
index 8c66ddf..9b7a39d 100644
--- a/src/main/scala/vscextension/extensionMain.scala
+++ b/src/main/scala/vscextension/extensionMain.scala
@@ -5,7 +5,6 @@ import scala.scalajs.js.annotation.JSExportTopLevel
import typings.vscode.mod as vscode
-
object extensionMain {
/** The main entry for the extension, called when activated first time.
From 7fd4b21390a9c43a85c073aac129f44b580ffb4b Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Sun, 20 Apr 2025 19:53:06 +0200
Subject: [PATCH 45/46] Update README to enhance clarity and detail current
features and getting started instructions
---
README.md | 40 ++++++++++++++++++----------------------
1 file changed, 18 insertions(+), 22 deletions(-)
diff --git a/README.md b/README.md
index 93b68a3..c744352 100644
--- a/README.md
+++ b/README.md
@@ -1,17 +1,21 @@
# functorcoder
**functorcoder** is an open source AI coding assistant utilizing LLM (Large Language Model) with algebraic and modular design in Scala.js. It aims at providing a clean and extensible architecture for AI coding assistants, which is helpful for understanding basic mechanics if you want to build your own AI coding assistant.
+current features implemented:
+- auto completion as you type
+- add documentation quick fix action
+
features aiming to implement:
- code generation: completion, documentation
- code modification: refactoring, optimization, bug fixing
- code analysis: code understanding, code review, code quality
-current features implemented:
-- auto completion as you type
-- add documentation quick fix action
-
## Getting Started
-Visit [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) to understand how to play with scala.js for VSCode extension development. Basically, sbt is used to build the project and run the extension.
+Visit [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) to understand how to play with scala.js for VSCode extension development. Basically, sbt is used to build the project and run the extension. There you will learn:
+- setting up the development environment
+- building the project and running the extension
+- packaging the extension
+
Before loading the extension, you need to add options to vscode user settings, and provide your OpenAI compatible API key and URL. Here is an example:
@@ -29,7 +33,7 @@ The project is divided into two main parts: the core module and the VSCode exten
**To get started**, read the file `extensionMain.scala` in the VSCode extension module. It is the main entry point for the extension.
-The first part is the core module, containing the main logic of the AI coding assistant:
+The first part is the core module, we aim keeping it concise. It contains the main logic of the ai coding assistant:
- Large Language Model (LLM) integration
- sending propmt to LLM and getting the response
@@ -46,23 +50,15 @@ project file structure for the core module:
```bash
/functorcoder
├── /src/main/scala/functorcoder
-│ ├── /llm
-│ │ ├── LLM.scala # Large Language Model (LLM) integration
+│ ├── /llm # Integration with LLM (e.g., OpenAI API)
│ ├── /actions
-│ │ ├── CodeCompletion.scala # Code completion module
-│ │ ├── Refactor.scala # Refactor code module
-│ │ └── Debug.scala # Debugging module
-│ ├── /types
-│ │ ├── InputTypes.scala # Types for code, context, and user actions
-│ │ └── OutputTypes.scala # Types for output (formatted code, suggestions)
-│ ├── /editorUI
-│ │ ├── EditorIntegration.scala # Integration with the editor (e.g., VSCode)
-│ └── /tests
-│ ├── CoreTests.scala # Unit tests for core modules
-└── /docs
- ├── README.md # Project overview and setup instructions
- ├── ARCHITECTURE.md # Architecture details and design decisions
- └── API.md # API documentation for integration
+│ │ ├── CodeGen.scala # Code completion, generation, and documentation
+│ │ ├── Commands.scala # Commands from functorcoder
+│ │ └── Debug.scala # Debugging module
+│ ├── /types # Types for code, context, and user actions
+│ ├── /editorUI # Integration with the editor (e.g., VSCode)
+│ └── /tests # Unit tests for core modules
+└── /docs # Documentation
```
The project file structure for the VSCode extension module:
From 3c53c64b2f9c00fcb384bec2c59bc9897f528355 Mon Sep 17 00:00:00 2001
From: doofin <8177dph@gmail.com>
Date: Mon, 21 Apr 2025 22:37:47 +0200
Subject: [PATCH 46/46] Update README to include next important features and
add a Chinese introduction
---
README.md | 11 ++++++++++-
1 file changed, 10 insertions(+), 1 deletion(-)
diff --git a/README.md b/README.md
index c744352..d121b9f 100644
--- a/README.md
+++ b/README.md
@@ -5,11 +5,20 @@ current features implemented:
- auto completion as you type
- add documentation quick fix action
-features aiming to implement:
+next important features to be implemented:
+- generate multiple files and folders
+- disable/enable auto completion
+
+features aiming to implement in long term:
- code generation: completion, documentation
- code modification: refactoring, optimization, bug fixing
- code analysis: code understanding, code review, code quality
+## 中文简介
+作为一个copilot的用户,最近受到国产开源模型deepseek的鼓励,希望能在开源社区中贡献一些自己的力量。目前已经有一些ai插件,比如copilot, tabnine,cursor等,还有一些开源的插件,比如continue。我看了下continue的代码,发现它的设计很复杂,代码并不简洁。目前,copilot的体验还可以,但是非常封闭,无法自定义很多地方,比如代码补全的长度,模型的选择等。开源的插件则有很多稳定性问题,bug不少。
+
+所以,作为一个scala的爱好者,也希望加深对llm应用的理解,我决定自己用scala.js来实现一个简单的ai助手。
+
## Getting Started
Visit [vscode-scalajs-hello](https://github.com/doofin/vscode-scalajs-hello) to understand how to play with scala.js for VSCode extension development. Basically, sbt is used to build the project and run the extension. There you will learn:
- setting up the development environment