{"id":28025590,"url":"https://github.com/elixir-tools/gen_lsp","last_synced_at":"2025-05-11T04:25:53.677Z","repository":{"id":49192964,"uuid":"512009015","full_name":"elixir-tools/gen_lsp","owner":"elixir-tools","description":"A behaviour for creating language servers. ","archived":false,"fork":false,"pushed_at":"2025-04-04T15:39:54.000Z","size":640,"stargazers_count":110,"open_issues_count":5,"forks_count":10,"subscribers_count":5,"default_branch":"main","last_synced_at":"2025-04-13T15:54:23.427Z","etag":null,"topics":["elixir","lsp"],"latest_commit_sha":null,"homepage":"","language":"Elixir","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/elixir-tools.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-07-08T19:43:27.000Z","updated_at":"2025-03-30T00:49:33.000Z","dependencies_parsed_at":"2023-01-28T06:46:20.009Z","dependency_job_id":"d124c716-a5a6-48d1-a540-05ddb839ff82","html_url":"https://github.com/elixir-tools/gen_lsp","commit_stats":null,"previous_names":["mhanberg/gen_lsp"],"tags_count":32,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-tools%2Fgen_lsp","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-tools%2Fgen_lsp/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-tools%2Fgen_lsp/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/elixir-tools%2Fgen_lsp/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/elixir-tools","download_url":"https://codeload.github.com/elixir-tools/gen_lsp/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":253518323,"owners_count":21921037,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["elixir","lsp"],"created_at":"2025-05-11T04:25:53.062Z","updated_at":"2025-05-11T04:25:53.649Z","avatar_url":"https://github.com/elixir-tools.png","language":"Elixir","funding_links":[],"categories":[],"sub_categories":[],"readme":"# GenLSP\n\n[![Discord](https://img.shields.io/badge/Discord-5865F3?style=flat\u0026logo=discord\u0026logoColor=white\u0026link=https://discord.gg/nNDMwTJ8)](https://discord.gg/6XdGnxVA2A)\n[![Hex.pm](https://img.shields.io/hexpm/v/gen_lsp)](https://hex.pm/packages/gen_lsp)\n[![Hex Docs](https://img.shields.io/badge/hex-docs-lightgreen.svg)](https://hexdocs.pm/gen_lsp/)\n[![GitHub Discussions](https://img.shields.io/github/discussions/elixir-tools/discussions)](https://github.com/orgs/elixir-tools/discussions)\n\n\u003c!-- MDOC !--\u003e\n\nGenLSP is an OTP behaviour for building processes that implement the [Language Server Protocol](https://microsoft.github.io/language-server-protocol/).\n\n## Examples\n\n\u003cdetails\u003e\n\u003csummary\u003e\u003ca href=\"https://github.com/rrrene/credo\"\u003eCredo\u003c/a\u003e language server.\u003c/summary\u003e\n\n\u003cpre\u003e\ndefmodule Credo.Lsp do\n  @moduledoc \"\"\"\n  LSP implementation for Credo.\n  \"\"\"\n  use GenLSP\n\n  alias GenLSP.Enumerations.TextDocumentSyncKind\n\n  alias GenLSP.Notifications.{\n    Exit,\n    Initialized,\n    TextDocumentDidChange,\n    TextDocumentDidClose,\n    TextDocumentDidOpen,\n    TextDocumentDidSave\n  }\n\n  alias GenLSP.Requests.{Initialize, Shutdown}\n\n  alias GenLSP.Structures.{\n    InitializeParams,\n    InitializeResult,\n    SaveOptions,\n    ServerCapabilities,\n    TextDocumentSyncOptions\n  }\n\n  alias Credo.Lsp.Cache, as: Diagnostics\n\n  def start_link(args) do\n    GenLSP.start_link(__MODULE__, args, [])\n  end\n\n  @impl true\n  def init(lsp, args) do\n    cache = Keyword.fetch!(args, :cache)\n\n    {:ok, assign(lsp, exit_code: 1, cache: cache)}\n  end\n\n  @impl true\n  def handle_request(%Initialize{params: %InitializeParams{root_uri: root_uri}}, lsp) do\n    {:reply,\n     %InitializeResult{\n       capabilities: %ServerCapabilities{\n         text_document_sync: %TextDocumentSyncOptions{\n           open_close: true,\n           save: %SaveOptions{include_text: true},\n           change: TextDocumentSyncKind.full()\n         }\n       },\n       server_info: %{name: \"Credo\"}\n     }, assign(lsp, root_uri: root_uri)}\n  end\n\n  def handle_request(%Shutdown{}, lsp) do\n    {:noreply, assign(lsp, exit_code: 0)}\n  end\n\n  @impl true\n  def handle_notification(%Initialized{}, lsp) do\n    GenLSP.log(lsp, :log, \"[Credo] LSP Initialized!\")\n    Diagnostics.refresh(lsp.assigns.cache, lsp)\n    Diagnostics.publish(lsp.assigns.cache, lsp)\n\n    {:noreply, lsp}\n  end\n\n  def handle_notification(%TextDocumentDidSave{}, lsp) do\n    Task.start_link(fn -\u003e\n      Diagnostics.clear(lsp.assigns.cache)\n      Diagnostics.refresh(lsp.assigns.cache, lsp)\n      Diagnostics.publish(lsp.assigns.cache, lsp)\n    end)\n\n    {:noreply, lsp}\n  end\n\n  def handle_notification(%TextDocumentDidChange{}, lsp) do\n    Task.start_link(fn -\u003e\n      Diagnostics.clear(lsp.assigns.cache)\n      Diagnostics.publish(lsp.assigns.cache, lsp)\n    end)\n\n    {:noreply, lsp}\n  end\n\n  def handle_notification(%note{}, lsp)\n      when note in [TextDocumentDidOpen, TextDocumentDidClose] do\n    {:noreply, lsp}\n  end\n\n  def handle_notification(%Exit{}, lsp) do\n    System.halt(lsp.assigns.exit_code)\n\n    {:noreply, lsp}\n  end\n\n  def handle_notification(_thing, lsp) do\n    {:noreply, lsp}\n  end\nend\n\n\ndefmodule Credo.Lsp.Cache do\n  @moduledoc \"\"\"\n  Cache for Credo diagnostics.\n  \"\"\"\n  use Agent\n\n  alias GenLSP.Structures.{\n    Diagnostic,\n    Position,\n    PublishDiagnosticsParams,\n    Range\n  }\n\n  alias GenLSP.Notifications.TextDocumentPublishDiagnostics\n\n  def start_link(_) do\n    Agent.start_link(fn -\u003e Map.new() end)\n  end\n\n  def refresh(cache, lsp) do\n    dir = URI.new!(lsp.assigns.root_uri).path\n\n    issues = Credo.Execution.get_issues(Credo.run([\"--strict\", \"--all\", \"#{dir}/**/*.ex\"]))\n\n    GenLSP.log(lsp, :info, \"[Credo] Found #{Enum.count(issues)} issues\")\n\n    for issue \u003c- issues do\n      diagnostic = %Diagnostic{\n        range: %Range{\n          start: %Position{line: issue.line_no - 1, character: issue.column || 0},\n          end: %Position{line: issue.line_no, character: 0}\n        },\n        severity: category_to_severity(issue.category),\n        message: \"\"\"\n        #{issue.message}\n\n        ## Explanation\n\n        #{issue.check.explanations()[:check]}\n        \"\"\"\n      }\n\n      put(cache, Path.absname(issue.filename), diagnostic)\n    end\n  end\n\n  def get(cache) do\n    Agent.get(cache, \u0026 \u00261)\n  end\n\n  def put(cache, filename, diagnostic) do\n    Agent.update(cache, fn cache -\u003e\n      Map.update(cache, Path.absname(filename), [diagnostic], fn v -\u003e\n        [diagnostic | v]\n      end)\n    end)\n  end\n\n  def clear(cache) do\n    Agent.update(cache, fn cache -\u003e\n      for {k, _} \u003c- cache, into: Map.new() do\n        {k, []}\n      end\n    end)\n  end\n\n  def publish(cache, lsp) do\n    for {file, diagnostics} \u003c- get(cache) do\n      GenLSP.notify(lsp, %TextDocumentPublishDiagnostics{\n        params: %PublishDiagnosticsParams{\n          uri: \"file://#{file}\",\n          diagnostics: diagnostics\n        }\n      })\n    end\n  end\n\n  def category_to_severity(:refactor), do: 1\n  def category_to_severity(:warning), do: 2\n  def category_to_severity(:design), do: 3\n  def category_to_severity(:consistency), do: 4\n  def category_to_severity(:readability), do: 4\nend\n\u003c/pre\u003e\n\n\u003c/details\u003e\n\n\u003c!-- MDOC !--\u003e\n\n## Built with GenLSP\n\n- [Next LS](https://github.com/elixir-tools/next-ls)\n- [credo-language-server](https://github.com/elixir-tools/credo-language-server)\n- [refactorex](https://github.com/gp-pereira/refactorex)\n\n## Thank Yous\n\n- Thank you to the [ElixirLS](https://github.com/elixir-lsp/elixir-ls) project for inspiration and answers to questions I had about the Language Server Protocol.\n\n## Installation\n\nThis package can be installed by adding `gen_lsp` to your list of dependencies in `mix.exs`:\n\n```elixir\ndef deps do\n  [\n    {:gen_lsp, \"~\u003e 0.6\"}\n  ]\nend\n```\n\nDocumentation can be found at \u003chttps://hexdocs.pm/gen_lsp\u003e.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-tools%2Fgen_lsp","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Felixir-tools%2Fgen_lsp","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Felixir-tools%2Fgen_lsp/lists"}