From 13670a5bd4df192922dbe6b1a40026e317bf3992 Mon Sep 17 00:00:00 2001 From: Mitchell Hanberg Date: Sun, 13 Aug 2023 16:03:52 -0400 Subject: [PATCH] refactor: migration system (#180) 1. Create the `schema` table if needed. This includes a version column. 2. If the max version selected from the `schema` table is equal to the current version, we noop and halt. Else, if the max version is less than the current one (compiled into this module) or nil we "upgrade" the database. 3. Unless halting, we drop the non-meta tables, and then create them from scratch 4. Return a value to signal to the caller that re-indexing is necessary. --- lib/next_ls.ex | 10 +- lib/next_ls/db.ex | 8 +- lib/next_ls/db/schema.ex | 117 ++++-- lib/next_ls/definition.ex | 2 +- lib/next_ls/runtime.ex | 11 +- lib/next_ls/runtime/supervisor.ex | 7 +- test/next_ls_test.exs | 669 +++++++++++++++--------------- 7 files changed, 452 insertions(+), 372 deletions(-) diff --git a/lib/next_ls.ex b/lib/next_ls.ex index ab087fa9..7bbeaf2c 100644 --- a/lib/next_ls.ex +++ b/lib/next_ls.ex @@ -133,7 +133,7 @@ defmodule NextLS do [] -> nil - [[_pk, _mod, file, _type, _name, line, column] | _] -> + [[_pk, _mod, file, _type, _name, line, column | _] | _] -> %Location{ uri: "file://#{file}", range: %Range{ @@ -236,6 +236,7 @@ defmodule NextLS do if query == "" do true else + # TODO: sqlite has a regexp feature, this can be done in sql most likely to_string(sym) =~ query end end @@ -596,7 +597,12 @@ defmodule NextLS do task = Task.Supervisor.async_nolink(lsp.assigns.task_supervisor, fn -> - {name, Runtime.compile(runtime_pid)} + {_, %{mode: mode}} = + dispatch(lsp.assigns.registry, :databases, fn entries -> + Enum.find(entries, fn {_, %{runtime: runtime}} -> runtime == name end) + end) + + {name, Runtime.compile(runtime_pid, force: mode == :reindex)} end) refresh_refs = Map.put(lsp.assigns.refresh_refs, task.ref, {token, "Compiled!"}) diff --git a/lib/next_ls/db.ex b/lib/next_ls/db.ex index 0c5adf34..10315294 100644 --- a/lib/next_ls/db.ex +++ b/lib/next_ls/db.ex @@ -27,10 +27,12 @@ defmodule NextLS.DB do registry = Keyword.fetch!(args, :registry) logger = Keyword.fetch!(args, :logger) activity = Keyword.fetch!(args, :activity) - Registry.register(registry, :databases, %{}) + runtime = Keyword.fetch!(args, :runtime) + {:ok, conn} = Exqlite.Basic.open(file) + {:ok, mode} = NextLS.DB.Schema.init({conn, logger}) - NextLS.DB.Schema.init({conn, logger}) + Registry.register(registry, :databases, %{mode: mode, runtime: runtime}) {:ok, %{ @@ -63,7 +65,7 @@ defmodule NextLS.DB do ) symbols = - for [_pk, module, file, type, name, line, column] <- rows do + for [_pk, module, file, type, name, line, column | _] <- rows do %{ module: module, file: file, diff --git a/lib/next_ls/db/schema.ex b/lib/next_ls/db/schema.ex index 63ac0530..4acd07e8 100644 --- a/lib/next_ls/db/schema.ex +++ b/lib/next_ls/db/schema.ex @@ -1,42 +1,109 @@ defmodule NextLS.DB.Schema do - @moduledoc false + @moduledoc """ + The Sqlite3 database schema. + + First, you are probably asking yourself, why doesn't this use Ecto? + + Well, because I didn't want to. And also because I am attempting to restrict this + project to as few dependencies as possible. + + The Ecto migration system is meant for highly durable data, that can't and shouldn't be lost, + whereas the data here is more like a fast and efficient cache. + + Rather than coming up with convoluted data migration strategies, we follow the following algorithm. + + 1. Create the `schema` table if needed. This includes a version column. + 2. If the max version selected from the `schema` table is equal to the current version, we noop and halt. + Else, if the max version is less than the current one (compiled into this module) or nil + we "upgrade" the database. + 3. Unless halting, we drop the non-meta tables, and then create them from scratch + 4. Return a value to signal to the caller that re-indexing is necessary. + """ import NextLS.DB.Query alias NextLS.DB + @version 2 + def init(conn) do + # FIXME: this is odd tech debt. not a big deal but is confusing + {_, logger} = conn + + NextLS.Logger.log(logger, "Beginning DB migration...") + DB.__query__( conn, ~Q""" - CREATE TABLE IF NOT EXISTS symbols ( + CREATE TABLE IF NOT EXISTS schema ( id integer PRIMARY KEY, - module text NOT NULL, - file text NOT NULL, - type text NOT NULL, - name text NOT NULL, - line integer NOT NULL, - column integer NOT NULL + version integer NOT NULL DEFAULT 1, + inserted_at text NOT NULL DEFAULT CURRENT_TIMESTAMP ); """, [] ) - DB.__query__( - conn, - ~Q""" - CREATE TABLE IF NOT EXISTS 'references' ( - id integer PRIMARY KEY, - identifier text NOT NULL, - arity integer, - file text NOT NULL, - type text NOT NULL, - module text NOT NULL, - start_line integer NOT NULL, - start_column integer NOT NULL, - end_line integer NOT NULL, - end_column integer NOT NULL) - """, - [] - ) + result = + case DB.__query__(conn, ~Q"SELECT MAX(version) FROM schema;", []) do + [[version]] when version == @version -> + NextLS.Logger.info(logger, "Database is on the latest version: #{@version}") + {:ok, :noop} + + result -> + version = with([[version]] <- result, do: version) || 0 + + NextLS.Logger.info(logger, """ + Database is being upgraded from version #{version} to #{@version}. + + This will trigger a full recompilation in order to re-index your codebase. + """) + + DB.__query__(conn, ~Q"INSERT INTO schema (version) VALUES (?);", [@version]) + + DB.__query__(conn, ~Q"DROP TABLE IF EXISTS symbols;", []) + DB.__query__(conn, ~Q"DROP TABLE IF EXISTS 'references';", []) + + DB.__query__( + conn, + ~Q""" + CREATE TABLE IF NOT EXISTS symbols ( + id integer PRIMARY KEY, + module text NOT NULL, + file text NOT NULL, + type text NOT NULL, + name text NOT NULL, + line integer NOT NULL, + column integer NOT NULL, + inserted_at text NOT NULL DEFAULT CURRENT_TIMESTAMP + ); + """, + [] + ) + + DB.__query__( + conn, + ~Q""" + CREATE TABLE IF NOT EXISTS 'references' ( + id integer PRIMARY KEY, + identifier text NOT NULL, + arity integer, + file text NOT NULL, + type text NOT NULL, + module text NOT NULL, + start_line integer NOT NULL, + start_column integer NOT NULL, + end_line integer NOT NULL, + end_column integer NOT NULL, + inserted_at text NOT NULL DEFAULT CURRENT_TIMESTAMP + ) + """, + [] + ) + + {:ok, :reindex} + end + + NextLS.Logger.log(logger, "Finished DB migration...") + result end end diff --git a/lib/next_ls/definition.ex b/lib/next_ls/definition.ex index d9d6f3f7..4dc304f0 100644 --- a/lib/next_ls/definition.ex +++ b/lib/next_ls/definition.ex @@ -5,7 +5,7 @@ defmodule NextLS.Definition do alias NextLS.DB def fetch(file, {line, col}, db) do - with [[_pk, identifier, _arity, _file, type, module, _start_l, _start_c, _end_l, _end_c]] <- + with [[_pk, identifier, _arity, _file, type, module, _start_l, _start_c, _end_l, _end_c | _]] <- DB.query( db, ~Q""" diff --git a/lib/next_ls/runtime.ex b/lib/next_ls/runtime.ex index c35a952e..1c974f8e 100644 --- a/lib/next_ls/runtime.ex +++ b/lib/next_ls/runtime.ex @@ -33,8 +33,8 @@ defmodule NextLS.Runtime do end end - def compile(server) do - GenServer.call(server, :compile, :infinity) + def compile(server, opts \\ []) do + GenServer.call(server, {:compile, opts}, :infinity) end @impl GenServer @@ -164,6 +164,7 @@ defmodule NextLS.Runtime do {:ok, %{ name: name, + working_dir: working_dir, compiler_ref: nil, port: port, task_supervisor: task_supervisor, @@ -197,9 +198,13 @@ defmodule NextLS.Runtime do {:reply, {:ok, reply}, state} end - def handle_call(:compile, from, %{node: node} = state) do + def handle_call({:compile, opts}, from, %{node: node} = state) do task = Task.Supervisor.async_nolink(state.task_supervisor, fn -> + if opts[:force] do + File.rm_rf!(Path.join(state.working_dir, ".elixir-tools/_build")) + end + case :rpc.call(node, :_next_ls_private_compiler, :compile, []) do {:badrpc, error} -> NextLS.Logger.error(state.logger, "Bad RPC call to node #{node}: #{inspect(error)}") diff --git a/lib/next_ls/runtime/supervisor.ex b/lib/next_ls/runtime/supervisor.ex index 29e8b621..32751e03 100644 --- a/lib/next_ls/runtime/supervisor.ex +++ b/lib/next_ls/runtime/supervisor.ex @@ -28,7 +28,12 @@ defmodule NextLS.Runtime.Supervisor do {NextLS.DB.Activity, logger: logger, name: db_activity, lsp: lsp, timeout: Application.get_env(:next_ls, :indexing_timeout)}, {NextLS.DB, - logger: logger, file: "#{hidden_folder}/nextls.db", registry: registry, name: db_name, activity: db_activity}, + logger: logger, + file: "#{hidden_folder}/nextls.db", + registry: registry, + name: db_name, + runtime: name, + activity: db_activity}, {NextLS.Runtime, init_arg[:runtime] ++ [name: name, registry: registry, parent: sidecar_name, db: db_name]} ] diff --git a/test/next_ls_test.exs b/test/next_ls_test.exs index 2e6101e3..1dcb2ff9 100644 --- a/test/next_ls_test.exs +++ b/test/next_ls_test.exs @@ -6,309 +6,360 @@ defmodule NextLSTest do @moduletag :tmp_dir - describe "one" do - @describetag root_paths: ["my_proj"] - setup %{tmp_dir: tmp_dir} do - File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib")) - File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs()) - [cwd: tmp_dir] - end + @moduletag root_paths: ["my_proj"] + setup %{tmp_dir: tmp_dir} do + File.mkdir_p!(Path.join(tmp_dir, "my_proj/lib")) + File.write!(Path.join(tmp_dir, "my_proj/mix.exs"), mix_exs()) + [cwd: tmp_dir] + end - setup %{tmp_dir: tmp_dir} do - File.write!(Path.join(tmp_dir, "my_proj/lib/bar.ex"), """ - defmodule Bar do - defstruct [:foo] + setup %{tmp_dir: tmp_dir} do + File.write!(Path.join(tmp_dir, "my_proj/lib/bar.ex"), """ + defmodule Bar do + defstruct [:foo] - def foo(arg1) do - end + def foo(arg1) do end - """) + end + """) - File.write!(Path.join(tmp_dir, "my_proj/lib/code_action.ex"), """ - defmodule Foo.CodeAction do - # some comment + File.write!(Path.join(tmp_dir, "my_proj/lib/code_action.ex"), """ + defmodule Foo.CodeAction do + # some comment - defmodule NestedMod do - def foo do - :ok - end + defmodule NestedMod do + def foo do + :ok end end - """) + end + """) - File.write!(Path.join(tmp_dir, "my_proj/lib/foo.ex"), """ - defmodule Foo do - end - """) + File.write!(Path.join(tmp_dir, "my_proj/lib/foo.ex"), """ + defmodule Foo do + end + """) - File.write!(Path.join(tmp_dir, "my_proj/lib/project.ex"), """ - defmodule Project do - def hello do - :world - end + File.write!(Path.join(tmp_dir, "my_proj/lib/project.ex"), """ + defmodule Project do + def hello do + :world end - """) - - File.rm_rf!(Path.join(tmp_dir, ".elixir-tools")) - - :ok end + """) - setup :with_lsp + File.rm_rf!(Path.join(tmp_dir, ".elixir-tools")) - test "can start the LSP server", %{server: server} do - assert alive?(server) - end + :ok + end - test "responds correctly to a shutdown request", %{client: client} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) + setup :with_lsp - assert_is_ready(context, "my_proj") + test "responds correctly to a shutdown request", %{client: client} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) - assert :ok == - request(client, %{ - method: "shutdown", - id: 2, - jsonrpc: "2.0" - }) + assert_is_ready(context, "my_proj") - assert_result 2, nil - end + assert :ok == + request(client, %{ + method: "shutdown", + id: 2, + jsonrpc: "2.0" + }) - test "returns method not found for unimplemented requests", %{client: client} do - id = System.unique_integer([:positive]) + assert_result 2, nil + end - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) + test "returns method not found for unimplemented requests", %{client: client} do + id = System.unique_integer([:positive]) + + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) + + assert :ok == + request(client, %{ + method: "textDocument/signatureHelp", + id: id, + jsonrpc: "2.0", + params: %{position: %{line: 0, character: 0}, textDocument: %{uri: ""}} + }) + + assert_notification "window/logMessage", %{ + "message" => "[NextLS] Method Not Found: textDocument/signatureHelp", + "type" => 2 + } + + assert_error ^id, %{ + "code" => -32_601, + "message" => "Method Not Found: textDocument/signatureHelp" + } + end - assert :ok == - request(client, %{ - method: "textDocument/signatureHelp", - id: id, - jsonrpc: "2.0", - params: %{position: %{line: 0, character: 0}, textDocument: %{uri: ""}} - }) + test "can initialize the server" do + assert_result 1, %{ + "capabilities" => %{ + "textDocumentSync" => %{ + "openClose" => true, + "save" => %{ + "includeText" => true + }, + "change" => 1 + } + }, + "serverInfo" => %{"name" => "Next LS"} + } + end - assert_notification "window/logMessage", %{ - "message" => "[NextLS] Method Not Found: textDocument/signatureHelp", - "type" => 2 - } + test "formats", %{client: client, cwd: cwd} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) - assert_error ^id, %{ - "code" => -32_601, - "message" => "Method Not Found: textDocument/signatureHelp" - } - end + notify client, %{ + method: "textDocument/didOpen", + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://#{cwd}/my_proj/lib/foo/bar.ex", + languageId: "elixir", + version: 1, + text: """ + defmodule Foo.Bar do + def run() do - test "can initialize the server" do - assert_result 1, %{ - "capabilities" => %{ - "textDocumentSync" => %{ - "openClose" => true, - "save" => %{ - "includeText" => true - }, - "change" => 1 - } - }, - "serverInfo" => %{"name" => "Next LS"} - } - end - test "formats", %{client: client, cwd: cwd} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) - - notify client, %{ - method: "textDocument/didOpen", - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: "file://#{cwd}/my_proj/lib/foo/bar.ex", - languageId: "elixir", - version: 1, - text: """ - defmodule Foo.Bar do - def run() do - - - :ok - end + :ok end - """ - } + end + """ } } - - request client, %{ - method: "textDocument/formatting", - id: 2, - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" - }, - options: %{ - insertSpaces: true, - tabSize: 2 - } + } + + request client, %{ + method: "textDocument/formatting", + id: 2, + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" + }, + options: %{ + insertSpaces: true, + tabSize: 2 } } + } - assert_result 2, nil + assert_result 2, nil - assert_is_ready(context, "my_proj") + assert_is_ready(context, "my_proj") - request client, %{ - method: "textDocument/formatting", - id: 3, - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" - }, - options: %{ - insertSpaces: true, - tabSize: 2 - } + request client, %{ + method: "textDocument/formatting", + id: 3, + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" + }, + options: %{ + insertSpaces: true, + tabSize: 2 } } + } - new_text = """ - defmodule Foo.Bar do - def run() do - :ok - end + new_text = """ + defmodule Foo.Bar do + def run() do + :ok end - """ - - assert_result 3, [ - %{ - "newText" => ^new_text, - "range" => %{"start" => %{"character" => 0, "line" => 0}, "end" => %{"character" => 0, "line" => 8}} - } - ] end + """ - test "formatting gracefully handles files with syntax errors", %{client: client, cwd: cwd} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) + assert_result 3, [ + %{ + "newText" => ^new_text, + "range" => %{"start" => %{"character" => 0, "line" => 0}, "end" => %{"character" => 0, "line" => 8}} + } + ] + end - notify client, %{ - method: "textDocument/didOpen", - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: "file://#{cwd}/my_proj/lib/foo/bar.ex", - languageId: "elixir", - version: 1, - text: """ - defmodule Foo.Bar do - def run() do + test "formatting gracefully handles files with syntax errors", %{client: client, cwd: cwd} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) + notify client, %{ + method: "textDocument/didOpen", + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://#{cwd}/my_proj/lib/foo/bar.ex", + languageId: "elixir", + version: 1, + text: """ + defmodule Foo.Bar do + def run() do - :ok - end - """ - } + + :ok + end + """ } } - - assert_is_ready(context, "my_proj") - - request client, %{ - method: "textDocument/formatting", - id: 2, - jsonrpc: "2.0", - params: %{ - textDocument: %{ - uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" - }, - options: %{ - insertSpaces: true, - tabSize: 2 - } + } + + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + + request client, %{ + method: "textDocument/formatting", + id: 2, + jsonrpc: "2.0", + params: %{ + textDocument: %{ + uri: "file://#{cwd}/my_proj/lib/foo/bar.ex" + }, + options: %{ + insertSpaces: true, + tabSize: 2 } } + } - assert_result 2, nil - end + assert_result 2, nil + end - test "workspace symbols", %{client: client, cwd: cwd} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) + test "workspace symbols", %{client: client, cwd: cwd} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - request client, %{ - method: "workspace/symbol", - id: 2, - jsonrpc: "2.0", - params: %{ - query: "" - } + request client, %{ + method: "workspace/symbol", + id: 2, + jsonrpc: "2.0", + params: %{ + query: "" } + } - assert_result 2, symbols + assert_result 2, symbols - assert %{ - "kind" => 12, - "location" => %{ - "range" => %{ - "start" => %{ - "line" => 3, - "character" => 0 - }, - "end" => %{ - "line" => 3, - "character" => 0 - } + assert %{ + "kind" => 12, + "location" => %{ + "range" => %{ + "start" => %{ + "line" => 3, + "character" => 0 }, - "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + "end" => %{ + "line" => 3, + "character" => 0 + } }, - "name" => "def foo" - } in symbols - - assert %{ - "kind" => 2, - "location" => %{ - "range" => %{ - "start" => %{ - "line" => 0, - "character" => 0 - }, - "end" => %{ - "line" => 0, - "character" => 0 - } + "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + }, + "name" => "def foo" + } in symbols + + assert %{ + "kind" => 2, + "location" => %{ + "range" => %{ + "start" => %{ + "line" => 0, + "character" => 0 }, - "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + "end" => %{ + "line" => 0, + "character" => 0 + } + }, + "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + }, + "name" => "defmodule Bar" + } in symbols + + assert %{ + "kind" => 23, + "location" => %{ + "range" => %{ + "start" => %{ + "line" => 1, + "character" => 0 + }, + "end" => %{ + "line" => 1, + "character" => 0 + } }, - "name" => "defmodule Bar" - } in symbols + "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + }, + "name" => "%Bar{}" + } in symbols + + assert %{ + "kind" => 2, + "location" => %{ + "range" => %{ + "start" => %{ + "line" => 3, + "character" => 0 + }, + "end" => %{ + "line" => 3, + "character" => 0 + } + }, + "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" + }, + "name" => "defmodule Foo.CodeAction.NestedMod" + } in symbols + end + + test "workspace symbols with query", %{client: client, cwd: cwd} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) + + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + + request client, %{ + method: "workspace/symbol", + id: 2, + jsonrpc: "2.0", + params: %{ + query: "fo" + } + } - assert %{ - "kind" => 23, + assert_result 2, symbols + + assert [ + %{ + "kind" => 12, "location" => %{ "range" => %{ "start" => %{ - "line" => 1, + "line" => 4, "character" => 0 }, "end" => %{ - "line" => 1, + "line" => 4, "character" => 0 } }, - "uri" => "file://#{cwd}/my_proj/lib/bar.ex" + "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" }, - "name" => "%Bar{}" - } in symbols - - assert %{ - "kind" => 2, + "name" => "def foo" + }, + %{ + "kind" => 12, "location" => %{ "range" => %{ "start" => %{ @@ -320,117 +371,61 @@ defmodule NextLSTest do "character" => 0 } }, - "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" + "uri" => "file://#{cwd}/my_proj/lib/bar.ex" }, - "name" => "defmodule Foo.CodeAction.NestedMod" - } in symbols - end - - test "workspace symbols with query", %{client: client, cwd: cwd} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) + "name" => "def foo" + } + ] == symbols + end - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} + test "deletes symbols when a file is deleted", %{client: client, cwd: cwd} = context do + assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) + assert_request(client, "client/registerCapability", fn _params -> nil end) - request client, %{ - method: "workspace/symbol", - id: 2, - jsonrpc: "2.0", - params: %{ - query: "fo" - } - } + assert_is_ready(context, "my_proj") + assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - assert_result 2, symbols - - assert [ - %{ - "kind" => 12, - "location" => %{ - "range" => %{ - "start" => %{ - "line" => 4, - "character" => 0 - }, - "end" => %{ - "line" => 4, - "character" => 0 - } - }, - "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" - }, - "name" => "def foo" - }, - %{ - "kind" => 12, - "location" => %{ - "range" => %{ - "start" => %{ - "line" => 3, - "character" => 0 - }, - "end" => %{ - "line" => 3, - "character" => 0 - } - }, - "uri" => "file://#{cwd}/my_proj/lib/bar.ex" - }, - "name" => "def foo" - } - ] == symbols - end + request client, %{method: "workspace/symbol", id: 2, jsonrpc: "2.0", params: %{query: ""}} - test "deletes symbols when a file is deleted", %{client: client, cwd: cwd} = context do - assert :ok == notify(client, %{method: "initialized", jsonrpc: "2.0", params: %{}}) - assert_request(client, "client/registerCapability", fn _params -> nil end) - - assert_is_ready(context, "my_proj") - assert_notification "$/progress", %{"value" => %{"kind" => "end", "message" => "Finished indexing!"}} - - request client, %{method: "workspace/symbol", id: 2, jsonrpc: "2.0", params: %{query: ""}} - - symbol = %{ - "kind" => 2, - "location" => %{ - "range" => %{ - "start" => %{ - "line" => 3, - "character" => 0 - }, - "end" => %{ - "line" => 3, - "character" => 0 - } + symbol = %{ + "kind" => 2, + "location" => %{ + "range" => %{ + "start" => %{ + "line" => 3, + "character" => 0 }, - "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" + "end" => %{ + "line" => 3, + "character" => 0 + } }, - "name" => "defmodule Foo.CodeAction.NestedMod" + "uri" => "file://#{cwd}/my_proj/lib/code_action.ex" + }, + "name" => "defmodule Foo.CodeAction.NestedMod" + } + + assert_result 2, symbols + + assert symbol in symbols + + notify(client, %{ + method: "workspace/didChangeWatchedFiles", + jsonrpc: "2.0", + params: %{ + changes: [ + %{ + type: GenLSP.Enumerations.FileChangeType.deleted(), + uri: "file://#{Path.join(cwd, "my_proj/lib/code_action.ex")}" + } + ] } + }) - assert_result 2, symbols + request client, %{method: "workspace/symbol", id: 3, jsonrpc: "2.0", params: %{query: ""}} - assert symbol in symbols - - notify(client, %{ - method: "workspace/didChangeWatchedFiles", - jsonrpc: "2.0", - params: %{ - changes: [ - %{ - type: GenLSP.Enumerations.FileChangeType.deleted(), - uri: "file://#{Path.join(cwd, "my_proj/lib/code_action.ex")}" - } - ] - } - }) + assert_result 3, symbols - request client, %{method: "workspace/symbol", id: 3, jsonrpc: "2.0", params: %{query: ""}} - - assert_result 3, symbols - - assert symbol not in symbols - end + assert symbol not in symbols end end