From defbe0924c5e312dbd5df7b2632248339f05f706 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Wed, 28 Jun 2023 00:46:38 -0400 Subject: [PATCH] wip --- lib/next_ls.ex | 19 +++- lib/next_ls/symbol_table.ex | 116 +++++++++++++++++++++-- priv/monkey/_next_ls_private_compiler.ex | 9 +- 3 files changed, 131 insertions(+), 13 deletions(-) diff --git a/lib/next_ls.ex b/lib/next_ls.ex index bcde85c6..df97fc0b 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -1,4 +1,7 @@ defmodule NextLS do + + + @moduledoc false use GenLSP @@ -18,6 +21,7 @@ defmodule NextLS do } alias GenLSP.Requests.{ + TextDocumentDocumentSymbol, Initialize, Shutdown, TextDocumentFormatting, @@ -38,7 +42,8 @@ defmodule NextLS do TextEdit, WorkDoneProgressBegin, WorkDoneProgressEnd, - SymbolInformation + SymbolInformation, + DocumentSymbol } alias NextLS.Runtime @@ -98,12 +103,22 @@ defmodule NextLS do change: TextDocumentSyncKind.full() }, document_formatting_provider: true, - workspace_symbol_provider: true + workspace_symbol_provider: true, + document_symbol_provider: true }, server_info: %{name: "NextLS"} }, assign(lsp, root_uri: root_uri)} end + def handle_request(%TextDocumentDocumentSymbol{params: %{text_document: %{uri: uri}}}, lsp) do + file = URI.parse(uri).path + + symbols = SymbolTable.symbols(lsp.assigns.symbol_table, file) |> List.wrap() + dbg symbols + + {:reply, symbols, lsp} + end + def handle_request(%WorkspaceSymbol{params: %{query: query}}, lsp) do filter = fn sym -> if query == "" do diff --git a/lib/next_ls/symbol_table.ex b/lib/next_ls/symbol_table.ex index 695676b7..c83daadb 100644 --- a/lib/next_ls/symbol_table.ex +++ b/lib/next_ls/symbol_table.ex @@ -1,9 +1,14 @@ defmodule NextLS.SymbolTable do + @moduledoc false use GenServer + alias GenLSP.Structures.DocumentSymbol + alias GenLSP.Structures.Range + alias GenLSP.Structures.Position + defmodule Symbol do - defstruct [:file, :module, :type, :name, :line, :col] + defstruct [:file, :module, :type, :name, :line, :col, :document] def new(args) do struct(__MODULE__, args) @@ -20,6 +25,9 @@ defmodule NextLS.SymbolTable do @spec symbols(pid() | atom()) :: list(struct()) def symbols(server), do: GenServer.call(server, :symbols) + @spec symbols(pid() | atom(), String.t()) :: list(struct()) + def symbols(server, file), do: GenServer.call(server, {:symbols, file}) + def close(server), do: GenServer.call(server, :close) def init(args) do @@ -36,10 +44,26 @@ defmodule NextLS.SymbolTable do {:ok, %{table: name}} end + def handle_call({:symbols, file}, _, state) do + symbols = + case :dets.lookup(state.table, file) do + [{_, symbols} | _rest] -> symbols + _ -> [] + end + + {:reply, symbols, state} + end + def handle_call(:symbols, _, state) do symbols = :dets.foldl( - fn {_key, symbol}, acc -> [symbol | acc] end, + fn {_key, symbol}, acc -> + if String.match?(to_string(symbol.name), ~r/__.*__/) do + acc + else + [symbol | acc] + end + end, [], state.table ) @@ -59,10 +83,17 @@ defmodule NextLS.SymbolTable do module_line: module_line, struct: struct, file: file, - defs: defs + defs: defs, + ast: ast } = symbols + {_new_ast, acc} = Macro.prewalk(ast, nil, &walker/2) + + document = File.read!(file) :dets.delete(state.table, mod) + :dets.delete(state.table, file) + + :dets.insert(state.table, {file, acc}) :dets.insert( state.table, @@ -73,7 +104,8 @@ defmodule NextLS.SymbolTable do type: :defmodule, name: Macro.to_string(mod), line: module_line, - col: 1 + col: 1, + document: document }} ) @@ -89,14 +121,13 @@ defmodule NextLS.SymbolTable do type: :defstruct, name: "%#{Macro.to_string(mod)}{}", line: meta[:line], - col: 1 + col: 1, + document: document }} ) end - for {name, {:v1, type, _meta, clauses}} <- defs, - not String.match?(to_string(name), ~r/__.*__/), - {meta, _, _, _} <- clauses do + for {name, {:v1, type, _meta, clauses}} <- defs, {meta, _, _, _} <- clauses do :dets.insert( state.table, {mod, @@ -106,11 +137,78 @@ defmodule NextLS.SymbolTable do type: type, name: name, line: meta[:line], - col: meta[:column] || 1 + col: meta[:column] || 1, + document: document }} ) end {:noreply, state} 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(kind) when kind in [:def, :defp, :defmacro, :defmacrop], + do: GenLSP.Enumerations.SymbolKind.function() + + defp walker({:defmodule, meta, [name | _children]} = macro, nil) do + {macro, + %DocumentSymbol{ + name: Macro.to_string(name) |> String.replace("\n", ""), + kind: GenLSP.Enumerations.SymbolKind.module(), + children: [], + range: %Range{ + start: %Position{line: meta[:do][:line] - 1, character: meta[:do][:column] - 1}, + end: %Position{line: meta[:end][:line] - 1, character: meta[:end][:column] - 1} + }, + selection_range: %Range{ + start: %Position{line: meta[:line] - 1, character: meta[:column] - 1}, + end: %Position{line: meta[:line] - 1, character: meta[:column] - 1} + } + }} + end + + # TODO: this needs to be a normal recursive function traversal, so that we don't walk + # these AST nodes anyway + defp walker({:defmodule, _meta, [_name | _children]} = macro, %DocumentSymbol{} = doc) do + {_, child} = Macro.prewalk(macro, nil, &walker/2) + + dbg(child) + {macro, %DocumentSymbol{doc | children: doc.children ++ [child]}} + end + + defp walker({type, meta, [name | _children]} = macro, %DocumentSymbol{} = root) + when type in [:def, :defp, :defmacro, :defmacro, :defstruct] do + {macro, + %DocumentSymbol{ + root + | children: + root.children ++ + [ + %DocumentSymbol{ + name: Macro.to_string(name) |> String.replace("\n", "") |> dbg(), + kind: elixir_kind_to_lsp_kind(type), + range: %Range{ + start: %Position{ + line: meta[:line] - 1, + character: meta[:column] - 1 + }, + end: %Position{ + line: (meta[:end] || meta[:end_of_expression] || meta)[:line] - 1, + character: (meta[:end] || meta[:end_of_expression] || meta)[:column] - 1 + } + }, + selection_range: %Range{ + start: %Position{line: meta[:line] - 1, character: meta[:column] - 1}, + end: %Position{line: meta[:line] - 1, character: meta[:column] - 1} + } + } + ] + }} + end + + defp walker(other, acc) do + {other, acc} + end end diff --git a/priv/monkey/_next_ls_private_compiler.ex b/priv/monkey/_next_ls_private_compiler.ex index 78eec97c..552e8675 100644 --- a/priv/monkey/_next_ls_private_compiler.ex +++ b/priv/monkey/_next_ls_private_compiler.ex @@ -11,11 +11,16 @@ defmodule NextLSPrivate.Tracer do {:ok, {_, [{'Dbgi', bin}]}} = :beam_lib.chunks(bytecode, ['Dbgi']) - {:debug_info_v1, _, {_, %{line: line, struct: struct}, _}} = :erlang.binary_to_term(bin) + ast = + env.file + |> File.read!() + |> Code.string_to_quoted!(token_metadata: true, columns: true) + + {:debug_info_v1, _, {_, %{line: line, struct: struct}, _} = foo} = :erlang.binary_to_term(bin) Process.send( parent, - {:tracer, %{file: env.file, module: env.module, module_line: line, struct: struct, defs: defs}}, + {:tracer, %{file: env.file, module: env.module, module_line: line, struct: struct, defs: defs, ast: ast}}, [] )