Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: swap out dets for sqlite3 #131

Merged
merged 3 commits into from
Jul 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion bin/start
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@

cd "$(dirname "$0")"/.. || exit 1

elixir --sname "next-ls-$RANDOM" -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"
iex --sname "next-ls-$RANDOM" -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"
64 changes: 36 additions & 28 deletions lib/next_ls.ex
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ defmodule NextLS do
@moduledoc false
use GenLSP

import NextLS.DB.Query

alias GenLSP.Enumerations.ErrorCodes
alias GenLSP.Enumerations.TextDocumentSyncKind
alias GenLSP.ErrorResponse
Expand Down Expand Up @@ -33,11 +35,11 @@ defmodule NextLS do
alias GenLSP.Structures.TextDocumentSyncOptions
alias GenLSP.Structures.TextEdit
alias GenLSP.Structures.WorkspaceFoldersChangeEvent
alias NextLS.DB
alias NextLS.Definition
alias NextLS.DiagnosticCache
alias NextLS.Progress
alias NextLS.Runtime
alias NextLS.SymbolTable

def start_link(args) do
{args, opts} =
Expand Down Expand Up @@ -120,21 +122,16 @@ defmodule NextLS do

def handle_request(%TextDocumentDefinition{params: %{text_document: %{uri: uri}, position: position}}, lsp) do
result =
dispatch(lsp.assigns.registry, :symbol_tables, fn entries ->
for {_, %{symbol_table: symbol_table, reference_table: ref_table}} <- entries do
case Definition.fetch(
URI.parse(uri).path,
{position.line + 1, position.character + 1},
symbol_table,
ref_table
) do
dispatch(lsp.assigns.registry, :databases, fn entries ->
for {pid, _} <- entries do
case Definition.fetch(URI.parse(uri).path, {position.line + 1, position.character + 1}, pid) do
nil ->
nil

[] ->
nil

[{file, line, column} | _] ->
[[_pk, _mod, file, _type, _name, line, column] | _] ->
%Location{
uri: "file://#{file}",
range: %Range{
Expand Down Expand Up @@ -184,10 +181,10 @@ defmodule NextLS do
end

symbols =
dispatch(lsp.assigns.registry, :symbol_tables, fn entries ->
for {pid, _} <- entries, %SymbolTable.Symbol{} = symbol <- SymbolTable.symbols(pid), filter.(symbol.name) do
dispatch(lsp.assigns.registry, :databases, fn entries ->
for {pid, _} <- entries, symbol <- DB.symbols(pid), filter.(symbol.name) do
name =
if symbol.type != :defstruct do
if symbol.type != "defstruct" do
"#{symbol.type} #{symbol.name}"
else
"#{symbol.name}"
Expand All @@ -201,11 +198,11 @@ defmodule NextLS do
range: %Range{
start: %Position{
line: symbol.line - 1,
character: symbol.col - 1
character: symbol.column - 1
},
end: %Position{
line: symbol.line - 1,
character: symbol.col - 1
character: symbol.column - 1
}
}
}
Expand Down Expand Up @@ -260,10 +257,6 @@ defmodule NextLS do
end

def handle_request(%Shutdown{}, lsp) do
dispatch(lsp.assigns.registry, :symbol_tables, fn entries ->
for {pid, _} <- entries, do: SymbolTable.close(pid)
end)

{:reply, nil, assign(lsp, exit_code: 0)}
end

Expand Down Expand Up @@ -323,6 +316,7 @@ defmodule NextLS do
path: Path.join(working_dir, ".elixir-tools"),
name: name,
registry: lsp.assigns.registry,
logger: lsp.assigns.logger,
runtime: [
task_supervisor: lsp.assigns.runtime_task_supervisor,
working_dir: working_dir,
Expand Down Expand Up @@ -463,16 +457,30 @@ defmodule NextLS do
def handle_notification(%WorkspaceDidChangeWatchedFiles{params: %DidChangeWatchedFilesParams{changes: changes}}, lsp) do
type = GenLSP.Enumerations.FileChangeType.deleted()

# TODO
# ✅ delete from documents
# ✅ delete all references that occur in this file
# ✅ delete all symbols from that file
lsp =
for %{type: ^type, uri: uri} <- changes, reduce: lsp do
lsp ->
dispatch(lsp.assigns.registry, :symbol_tables, fn entries ->
dispatch(lsp.assigns.registry, :databases, fn entries ->
for {pid, _} <- entries do
SymbolTable.remove(pid, uri)
file = URI.parse(uri).path

NextLS.DB.query(
pid,
~Q"""
DELETE FROM symbols
WHERE symbols.file = ?;
""",
[file]
)

NextLS.DB.query(
pid,
~Q"""
DELETE FROM 'references' AS refs
WHERE refs.file = ?;
""",
[file]
)
end
end)

Expand Down Expand Up @@ -563,10 +571,10 @@ defmodule NextLS do
end
end

defp elixir_kind_to_lsp_kind(:defmodule), do: GenLSP.Enumerations.SymbolKind.module()
defp elixir_kind_to_lsp_kind(:defstruct), do: GenLSP.Enumerations.SymbolKind.struct()
defp elixir_kind_to_lsp_kind("defmodule"), do: GenLSP.Enumerations.SymbolKind.module()
defp elixir_kind_to_lsp_kind("defstruct"), do: GenLSP.Enumerations.SymbolKind.struct()

defp elixir_kind_to_lsp_kind(kind) when kind in [:def, :defp, :defmacro, :defmacrop],
defp elixir_kind_to_lsp_kind(kind) when kind in ["def", "defp", "defmacro", "defmacrop"],
do: GenLSP.Enumerations.SymbolKind.function()

# NOTE: this is only possible because the registry is not partitioned
Expand Down
193 changes: 193 additions & 0 deletions lib/next_ls/db.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,193 @@
defmodule NextLS.DB do
@moduledoc false
use GenServer

import __MODULE__.Query

@type query :: String.t()

def start_link(args) do
GenServer.start_link(__MODULE__, args, Keyword.take(args, [:name]))
end

@spec query(pid(), query(), list()) :: list()
def query(server, query, args \\ []), do: GenServer.call(server, {:query, query, args})

@spec symbols(pid()) :: list(map())
def symbols(server), do: GenServer.call(server, :symbols)

@spec insert_symbol(pid(), map()) :: :ok
def insert_symbol(server, payload), do: GenServer.cast(server, {:insert_symbol, payload})

@spec insert_reference(pid(), map()) :: :ok
def insert_reference(server, payload), do: GenServer.cast(server, {:insert_reference, payload})

def init(args) do
file = Keyword.fetch!(args, :file)
registry = Keyword.fetch!(args, :registry)
logger = Keyword.fetch!(args, :logger)
Registry.register(registry, :databases, %{})
{:ok, conn} = :esqlite3.open(file)

NextLS.DB.Schema.init({conn, logger})

{:ok,
%{
conn: conn,
file: file,
logger: logger
}}
end

def handle_call({:query, query, args}, _from, %{conn: conn} = s) do
rows = __query__({conn, s.logger}, query, args)

{:reply, rows, s}
end

def handle_call(:symbols, _from, %{conn: conn} = s) do
rows =
__query__(
{conn, s.logger},
~Q"""
SELECT
*
FROM
symbols;
""",
[]
)

symbols =
for [_pk, module, file, type, name, line, column] <- rows do
%{
module: module,
file: file,
type: type,
name: name,
line: line,
column: column
}
end

{:reply, symbols, s}
end

def handle_cast({:insert_symbol, symbol}, %{conn: conn} = s) do
%{
module: mod,
module_line: module_line,
struct: struct,
file: file,
defs: defs
} = symbol

__query__(
{conn, s.logger},
~Q"""
DELETE FROM symbols
WHERE module = ?;
""",
[mod]
)

__query__(
{conn, s.logger},
~Q"""
INSERT INTO symbols (module, file, type, name, line, 'column')
VALUES (?, ?, ?, ?, ?, ?);
""",
[mod, file, "defmodule", mod, module_line, 1]
)

if struct do
{_, _, meta, _} = defs[:__struct__]

__query__(
{conn, s.logger},
~Q"""
INSERT INTO symbols (module, file, type, name, line, 'column')
VALUES (?, ?, ?, ?, ?, ?);
""",
[mod, file, "defstruct", "%#{Macro.to_string(mod)}{}", meta[:line], 1]
)
end

for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do
__query__(
{conn, s.logger},
~Q"""
INSERT INTO symbols (module, file, type, name, line, 'column')
VALUES (?, ?, ?, ?, ?, ?);
""",
[mod, file, type, name, meta[:line], meta[:column] || 1]
)
end

{:noreply, s}
end

def handle_cast({:insert_reference, reference}, %{conn: conn} = s) do
%{
meta: meta,
identifier: identifier,
file: file,
type: type,
module: module
} = reference

col = meta[:column] || 0

{{start_line, start_column}, {end_line, end_column}} =
{{meta[:line], col},
{meta[:line], col + String.length(identifier |> to_string() |> String.replace("Elixir.", ""))}}

__query__(
{conn, s.logger},
~Q"""
DELETE FROM 'references' AS refs
WHERE refs.file = ?
AND (? <= refs.start_line
AND refs.start_line <= ?
AND ? <= refs.start_column
AND refs.start_column <= ?)
OR (? <= refs.end_line
AND refs.end_line <= ?
AND ? <= refs.end_column
AND refs.end_column <= ?);
""",
[file, start_line, end_line, start_column, end_column, start_line, end_line, start_column, end_column]
)

__query__(
{conn, s.logger},
~Q"""
INSERT INTO 'references' (identifier, arity, file, type, module, start_line, start_column, end_line, end_column)
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?);
""",
[identifier, reference[:arity], file, type, module, start_line, start_column, end_line, end_column]
)

{:noreply, s}
end

def __query__({conn, logger}, query, args) do
args = Enum.map(args, &cast/1)

with {:error, _e} <- :esqlite3.q(conn, query, cast(args)) do
error = :esqlite3.error_info(conn).errmsg
NextLS.Logger.error(logger, error)
{:error, error}
end
end

defp cast(arg) do
cond do
is_atom(arg) and String.starts_with?(to_string(arg), "Elixir.") ->
Macro.to_string(arg)

true ->
arg
end
end
end
18 changes: 18 additions & 0 deletions lib/next_ls/db/format.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
defmodule NextLS.DB.Format do
@moduledoc false
# @behaviour Mix.Tasks.Format

# @impl Mix.Tasks.Format
# def features(_opts), do: [sigils: [:Q], extensions: []]

# @impl Mix.Tasks.Format
# def format(input, _formatter_opts, _opts \\ []) do
# path = Path.join(System.tmp_dir!(), "#{System.unique_integer()}-temp.sql")
# File.write!(path, input)
# {result, 0} = System.cmd("pg_format", [path])

# File.rm!(path)

# String.trim(result) <> "\n"
# end
end
6 changes: 6 additions & 0 deletions lib/next_ls/db/query.ex
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
defmodule NextLS.DB.Query do
@moduledoc false
defmacro sigil_Q({:<<>>, _, [bin]}, _mods) do
bin
end
end
Loading
Loading