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