Current section

29 Versions

Jump to

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…