Current section
29 Versions
Jump to
Current section
29 Versions
Compare versions
12
files changed
+361
additions
-264
deletions
| @@ -4,10 +4,12 @@ | |
| 4 4 | {<<"elixir">>,<<"~> 1.13">>}. |
| 5 5 | {<<"files">>, |
| 6 6 | [<<"lib">>,<<"lib/next_ls.ex">>,<<"lib/next_ls">>, |
| 7 | - <<"lib/next_ls/document_symbol.ex">>,<<"lib/next_ls/diagnostic_cache.ex">>, |
| 7 | + <<"lib/next_ls/document_symbol.ex">>,<<"lib/next_ls/db">>, |
| 8 | + <<"lib/next_ls/db/format.ex">>,<<"lib/next_ls/db/schema.ex">>, |
| 9 | + <<"lib/next_ls/db/query.ex">>,<<"lib/next_ls/diagnostic_cache.ex">>, |
| 8 10 | <<"lib/next_ls/runtime">>,<<"lib/next_ls/runtime/supervisor.ex">>, |
| 9 11 | <<"lib/next_ls/runtime/sidecar.ex">>,<<"lib/next_ls/lsp_supervisor.ex">>, |
| 10 | - <<"lib/next_ls/symbol_table.ex">>,<<"lib/next_ls/application.ex">>, |
| 12 | + <<"lib/next_ls/db.ex">>,<<"lib/next_ls/application.ex">>, |
| 11 13 | <<"lib/next_ls/definition.ex">>,<<"lib/next_ls/logger.ex">>, |
| 12 14 | <<"lib/next_ls/extensions">>, |
| 13 15 | <<"lib/next_ls/extensions/elixir_extension.ex">>, |
| @@ -26,5 +28,10 @@ | |
| 26 28 | {<<"name">>,<<"gen_lsp">>}, |
| 27 29 | {<<"optional">>,false}, |
| 28 30 | {<<"repository">>,<<"hexpm">>}, |
| 29 | - {<<"requirement">>,<<"~> 0.5">>}]]}. |
| 30 | - {<<"version">>,<<"0.6.1">>}. |
| 31 | + {<<"requirement">>,<<"~> 0.5">>}], |
| 32 | + [{<<"app">>,<<"esqlite">>}, |
| 33 | + {<<"name">>,<<"esqlite">>}, |
| 34 | + {<<"optional">>,false}, |
| 35 | + {<<"repository">>,<<"hexpm">>}, |
| 36 | + {<<"requirement">>,<<"~> 0.8.6">>}]]}. |
| 37 | + {<<"version">>,<<"0.6.2">>}. |
| @@ -2,6 +2,8 @@ defmodule NextLS do | |
| 2 2 | @moduledoc false |
| 3 3 | use GenLSP |
| 4 4 | |
| 5 | + import NextLS.DB.Query |
| 6 | + |
| 5 7 | alias GenLSP.Enumerations.ErrorCodes |
| 6 8 | alias GenLSP.Enumerations.TextDocumentSyncKind |
| 7 9 | alias GenLSP.ErrorResponse |
| @@ -33,11 +35,11 @@ defmodule NextLS do | |
| 33 35 | alias GenLSP.Structures.TextDocumentSyncOptions |
| 34 36 | alias GenLSP.Structures.TextEdit |
| 35 37 | alias GenLSP.Structures.WorkspaceFoldersChangeEvent |
| 38 | + alias NextLS.DB |
| 36 39 | alias NextLS.Definition |
| 37 40 | alias NextLS.DiagnosticCache |
| 38 41 | alias NextLS.Progress |
| 39 42 | alias NextLS.Runtime |
| 40 | - alias NextLS.SymbolTable |
| 41 43 | |
| 42 44 | def start_link(args) do |
| 43 45 | {args, opts} = |
| @@ -120,21 +122,16 @@ defmodule NextLS do | |
| 120 122 | |
| 121 123 | def handle_request(%TextDocumentDefinition{params: %{text_document: %{uri: uri}, position: position}}, lsp) do |
| 122 124 | result = |
| 123 | - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> |
| 124 | - for {_, %{symbol_table: symbol_table, reference_table: ref_table}} <- entries do |
| 125 | - case Definition.fetch( |
| 126 | - URI.parse(uri).path, |
| 127 | - {position.line + 1, position.character + 1}, |
| 128 | - symbol_table, |
| 129 | - ref_table |
| 130 | - ) do |
| 125 | + dispatch(lsp.assigns.registry, :databases, fn entries -> |
| 126 | + for {pid, _} <- entries do |
| 127 | + case Definition.fetch(URI.parse(uri).path, {position.line + 1, position.character + 1}, pid) do |
| 131 128 | nil -> |
| 132 129 | nil |
| 133 130 | |
| 134 131 | [] -> |
| 135 132 | nil |
| 136 133 | |
| 137 | - [{file, line, column} | _] -> |
| 134 | + [[_pk, _mod, file, _type, _name, line, column] | _] -> |
| 138 135 | %Location{ |
| 139 136 | uri: "file://#{file}", |
| 140 137 | range: %Range{ |
| @@ -184,10 +181,10 @@ defmodule NextLS do | |
| 184 181 | end |
| 185 182 | |
| 186 183 | symbols = |
| 187 | - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> |
| 188 | - for {pid, _} <- entries, %SymbolTable.Symbol{} = symbol <- SymbolTable.symbols(pid), filter.(symbol.name) do |
| 184 | + dispatch(lsp.assigns.registry, :databases, fn entries -> |
| 185 | + for {pid, _} <- entries, symbol <- DB.symbols(pid), filter.(symbol.name) do |
| 189 186 | name = |
| 190 | - if symbol.type != :defstruct do |
| 187 | + if symbol.type != "defstruct" do |
| 191 188 | "#{symbol.type} #{symbol.name}" |
| 192 189 | else |
| 193 190 | "#{symbol.name}" |
| @@ -201,11 +198,11 @@ defmodule NextLS do | |
| 201 198 | range: %Range{ |
| 202 199 | start: %Position{ |
| 203 200 | line: symbol.line - 1, |
| 204 | - character: symbol.col - 1 |
| 201 | + character: symbol.column - 1 |
| 205 202 | }, |
| 206 203 | end: %Position{ |
| 207 204 | line: symbol.line - 1, |
| 208 | - character: symbol.col - 1 |
| 205 | + character: symbol.column - 1 |
| 209 206 | } |
| 210 207 | } |
| 211 208 | } |
| @@ -260,10 +257,6 @@ defmodule NextLS do | |
| 260 257 | end |
| 261 258 | |
| 262 259 | def handle_request(%Shutdown{}, lsp) do |
| 263 | - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> |
| 264 | - for {pid, _} <- entries, do: SymbolTable.close(pid) |
| 265 | - end) |
| 266 | - |
| 267 260 | {:reply, nil, assign(lsp, exit_code: 0)} |
| 268 261 | end |
| 269 262 | |
| @@ -323,6 +316,7 @@ defmodule NextLS do | |
| 323 316 | path: Path.join(working_dir, ".elixir-tools"), |
| 324 317 | name: name, |
| 325 318 | registry: lsp.assigns.registry, |
| 319 | + logger: lsp.assigns.logger, |
| 326 320 | runtime: [ |
| 327 321 | task_supervisor: lsp.assigns.runtime_task_supervisor, |
| 328 322 | working_dir: working_dir, |
| @@ -463,16 +457,30 @@ defmodule NextLS do | |
| 463 457 | def handle_notification(%WorkspaceDidChangeWatchedFiles{params: %DidChangeWatchedFilesParams{changes: changes}}, lsp) do |
| 464 458 | type = GenLSP.Enumerations.FileChangeType.deleted() |
| 465 459 | |
| 466 | - # TODO |
| 467 | - # âś… delete from documents |
| 468 | - # âś… delete all references that occur in this file |
| 469 | - # âś… delete all symbols from that file |
| 470 460 | lsp = |
| 471 461 | for %{type: ^type, uri: uri} <- changes, reduce: lsp do |
| 472 462 | lsp -> |
| 473 | - dispatch(lsp.assigns.registry, :symbol_tables, fn entries -> |
| 463 | + dispatch(lsp.assigns.registry, :databases, fn entries -> |
| 474 464 | for {pid, _} <- entries do |
| 475 | - SymbolTable.remove(pid, uri) |
| 465 | + file = URI.parse(uri).path |
| 466 | + |
| 467 | + NextLS.DB.query( |
| 468 | + pid, |
| 469 | + ~Q""" |
| 470 | + DELETE FROM symbols |
| 471 | + WHERE symbols.file = ?; |
| 472 | + """, |
| 473 | + [file] |
| 474 | + ) |
| 475 | + |
| 476 | + NextLS.DB.query( |
| 477 | + pid, |
| 478 | + ~Q""" |
| 479 | + DELETE FROM 'references' AS refs |
| 480 | + WHERE refs.file = ?; |
| 481 | + """, |
| 482 | + [file] |
| 483 | + ) |
| 476 484 | end |
| 477 485 | end) |
| 478 486 | |
| @@ -563,10 +571,10 @@ defmodule NextLS do | |
| 563 571 | end |
| 564 572 | end |
| 565 573 | |
| 566 | - defp elixir_kind_to_lsp_kind(:defmodule), do: GenLSP.Enumerations.SymbolKind.module() |
| 567 | - defp elixir_kind_to_lsp_kind(:defstruct), do: GenLSP.Enumerations.SymbolKind.struct() |
| 574 | + defp elixir_kind_to_lsp_kind("defmodule"), do: GenLSP.Enumerations.SymbolKind.module() |
| 575 | + defp elixir_kind_to_lsp_kind("defstruct"), do: GenLSP.Enumerations.SymbolKind.struct() |
| 568 576 | |
| 569 | - defp elixir_kind_to_lsp_kind(kind) when kind in [:def, :defp, :defmacro, :defmacrop], |
| 577 | + defp elixir_kind_to_lsp_kind(kind) when kind in ["def", "defp", "defmacro", "defmacrop"], |
| 570 578 | do: GenLSP.Enumerations.SymbolKind.function() |
| 571 579 | |
| 572 580 | # NOTE: this is only possible because the registry is not partitioned |
| @@ -0,0 +1,193 @@ | |
| 1 | + defmodule NextLS.DB do |
| 2 | + @moduledoc false |
| 3 | + use GenServer |
| 4 | + |
| 5 | + import __MODULE__.Query |
| 6 | + |
| 7 | + @type query :: String.t() |
| 8 | + |
| 9 | + def start_link(args) do |
| 10 | + GenServer.start_link(__MODULE__, args, Keyword.take(args, [:name])) |
| 11 | + end |
| 12 | + |
| 13 | + @spec query(pid(), query(), list()) :: list() |
| 14 | + def query(server, query, args \\ []), do: GenServer.call(server, {:query, query, args}) |
| 15 | + |
| 16 | + @spec symbols(pid()) :: list(map()) |
| 17 | + def symbols(server), do: GenServer.call(server, :symbols) |
| 18 | + |
| 19 | + @spec insert_symbol(pid(), map()) :: :ok |
| 20 | + def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload}) |
| 21 | + |
| 22 | + @spec insert_reference(pid(), map()) :: :ok |
| 23 | + def insert_reference(server, payload), do: GenServer.cast(server, {:insert_reference, payload}) |
| 24 | + |
| 25 | + def init(args) do |
| 26 | + file = Keyword.fetch!(args, :file) |
| 27 | + registry = Keyword.fetch!(args, :registry) |
| 28 | + logger = Keyword.fetch!(args, :logger) |
| 29 | + Registry.register(registry, :databases, %{}) |
| 30 | + {:ok, conn} = :esqlite3.open(file) |
| 31 | + |
| 32 | + NextLS.DB.Schema.init({conn, logger}) |
| 33 | + |
| 34 | + {:ok, |
| 35 | + %{ |
| 36 | + conn: conn, |
| 37 | + file: file, |
| 38 | + logger: logger |
| 39 | + }} |
| 40 | + end |
| 41 | + |
| 42 | + def handle_call({:query, query, args}, _from, %{conn: conn} = s) do |
| 43 | + rows = __query__({conn, s.logger}, query, args) |
| 44 | + |
| 45 | + {:reply, rows, s} |
| 46 | + end |
| 47 | + |
| 48 | + def handle_call(:symbols, _from, %{conn: conn} = s) do |
| 49 | + rows = |
| 50 | + __query__( |
| 51 | + {conn, s.logger}, |
| 52 | + ~Q""" |
| 53 | + SELECT |
| 54 | + * |
| 55 | + FROM |
| 56 | + symbols; |
| 57 | + """, |
| 58 | + [] |
| 59 | + ) |
| 60 | + |
| 61 | + symbols = |
| 62 | + for [_pk, module, file, type, name, line, column] <- rows do |
| 63 | + %{ |
| 64 | + module: module, |
| 65 | + file: file, |
| 66 | + type: type, |
| 67 | + name: name, |
| 68 | + line: line, |
| 69 | + column: column |
| 70 | + } |
| 71 | + end |
| 72 | + |
| 73 | + {:reply, symbols, s} |
| 74 | + end |
| 75 | + |
| 76 | + def handle_cast({:insert_symbol, symbol}, %{conn: conn} = s) do |
| 77 | + %{ |
| 78 | + module: mod, |
| 79 | + module_line: module_line, |
| 80 | + struct: struct, |
| 81 | + file: file, |
| 82 | + defs: defs |
| 83 | + } = symbol |
| 84 | + |
| 85 | + __query__( |
| 86 | + {conn, s.logger}, |
| 87 | + ~Q""" |
| 88 | + DELETE FROM symbols |
| 89 | + WHERE module = ?; |
| 90 | + """, |
| 91 | + [mod] |
| 92 | + ) |
| 93 | + |
| 94 | + __query__( |
| 95 | + {conn, s.logger}, |
| 96 | + ~Q""" |
| 97 | + INSERT INTO symbols (module, file, type, name, line, 'column') |
| 98 | + VALUES (?, ?, ?, ?, ?, ?); |
| 99 | + """, |
| 100 | + [mod, file, "defmodule", mod, module_line, 1] |
| 101 | + ) |
| 102 | + |
| 103 | + if struct do |
| 104 | + {_, _, meta, _} = defs[:__struct__] |
| 105 | + |
| 106 | + __query__( |
| 107 | + {conn, s.logger}, |
| 108 | + ~Q""" |
| 109 | + INSERT INTO symbols (module, file, type, name, line, 'column') |
| 110 | + VALUES (?, ?, ?, ?, ?, ?); |
| 111 | + """, |
| 112 | + [mod, file, "defstruct", "%#{Macro.to_string(mod)}{}", meta[:line], 1] |
| 113 | + ) |
| 114 | + end |
| 115 | + |
| 116 | + for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do |
| 117 | + __query__( |
| 118 | + {conn, s.logger}, |
| 119 | + ~Q""" |
| 120 | + INSERT INTO symbols (module, file, type, name, line, 'column') |
| 121 | + VALUES (?, ?, ?, ?, ?, ?); |
| 122 | + """, |
| 123 | + [mod, file, type, name, meta[:line], meta[:column] || 1] |
| 124 | + ) |
| 125 | + end |
| 126 | + |
| 127 | + {:noreply, s} |
| 128 | + end |
| 129 | + |
| 130 | + def handle_cast({:insert_reference, reference}, %{conn: conn} = s) do |
| 131 | + %{ |
| 132 | + meta: meta, |
| 133 | + identifier: identifier, |
| 134 | + file: file, |
| 135 | + type: type, |
| 136 | + module: module |
| 137 | + } = reference |
| 138 | + |
| 139 | + col = meta[:column] || 0 |
| 140 | + |
| 141 | + {{start_line, start_column}, {end_line, end_column}} = |
| 142 | + {{meta[:line], col}, |
| 143 | + {meta[:line], col + String.length(identifier |> to_string() |> String.replace("Elixir.", ""))}} |
| 144 | + |
| 145 | + __query__( |
| 146 | + {conn, s.logger}, |
| 147 | + ~Q""" |
| 148 | + DELETE FROM 'references' AS refs |
| 149 | + WHERE refs.file = ? |
| 150 | + AND (? <= refs.start_line |
| 151 | + AND refs.start_line <= ? |
| 152 | + AND ? <= refs.start_column |
| 153 | + AND refs.start_column <= ?) |
| 154 | + OR (? <= refs.end_line |
| 155 | + AND refs.end_line <= ? |
| 156 | + AND ? <= refs.end_column |
| 157 | + AND refs.end_column <= ?); |
| 158 | + """, |
| 159 | + [file, start_line, end_line, start_column, end_column, start_line, end_line, start_column, end_column] |
| 160 | + ) |
| 161 | + |
| 162 | + __query__( |
| 163 | + {conn, s.logger}, |
| 164 | + ~Q""" |
| 165 | + INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column) |
| 166 | + VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?); |
| 167 | + """, |
| 168 | + [identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column] |
| 169 | + ) |
| 170 | + |
| 171 | + {:noreply, s} |
| 172 | + end |
| 173 | + |
| 174 | + def __query__({conn, logger}, query, args) do |
| 175 | + args = Enum.map(args, &cast/1) |
| 176 | + |
| 177 | + with {:error, _e} <- :esqlite3.q(conn, query, cast(args)) do |
| 178 | + error = :esqlite3.error_info(conn).errmsg |
| 179 | + NextLS.Logger.error(logger, error) |
| 180 | + {:error, error} |
| 181 | + end |
| 182 | + end |
| 183 | + |
| 184 | + defp cast(arg) do |
| 185 | + cond do |
| 186 | + is_atom(arg) and String.starts_with?(to_string(arg), "Elixir.") -> |
| 187 | + Macro.to_string(arg) |
| 188 | + |
| 189 | + true -> |
| 190 | + arg |
| 191 | + end |
| 192 | + end |
| 193 | + end |
| @@ -0,0 +1,18 @@ | |
| 1 | + defmodule NextLS.DB.Format do |
| 2 | + @moduledoc false |
| 3 | + # @behaviour Mix.Tasks.Format |
| 4 | + |
| 5 | + # @impl Mix.Tasks.Format |
| 6 | + # def features(_opts), do: [sigils: [:Q], extensions: []] |
| 7 | + |
| 8 | + # @impl Mix.Tasks.Format |
| 9 | + # def format(input, _formatter_opts, _opts \\ []) do |
| 10 | + # path = Path.join(System.tmp_dir!(), "#{System.unique_integer()}-temp.sql") |
| 11 | + # File.write!(path, input) |
| 12 | + # {result, 0} = System.cmd("pg_format", [path]) |
| 13 | + |
| 14 | + # File.rm!(path) |
| 15 | + |
| 16 | + # String.trim(result) <> "\n" |
| 17 | + # end |
| 18 | + end |
| @@ -0,0 +1,6 @@ | |
| 1 | + defmodule NextLS.DB.Query do |
| 2 | + @moduledoc false |
| 3 | + defmacro sigil_Q({:<<>>, _, [bin]}, _mods) do |
| 4 | + bin |
| 5 | + end |
| 6 | + end |
Loading more files…