Skip to content

Commit b4b54a8

Browse files
committed
fix: support the case where the root folder contains subprojects
1 parent cf379a5 commit b4b54a8

File tree

19 files changed

+202
-80
lines changed

19 files changed

+202
-80
lines changed
Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,50 @@
1+
defmodule Expert.ActiveProjects do
2+
@moduledoc """
3+
A cache to keep track of active projects.
4+
5+
Since GenLSP events happen asynchronously, we use an ets table to keep track of
6+
them and avoid race conditions when we try to update the list of active projects.
7+
"""
8+
9+
use GenServer
10+
11+
def child_spec(_) do
12+
%{
13+
id: __MODULE__,
14+
start: {__MODULE__, :start_link, []}
15+
}
16+
end
17+
18+
def start_link do
19+
GenServer.start_link(__MODULE__, [], name: __MODULE__)
20+
end
21+
22+
def init(_) do
23+
__MODULE__ = :ets.new(__MODULE__, [:set, :named_table, :public, read_concurrency: true])
24+
{:ok, nil}
25+
end
26+
27+
def projects do
28+
__MODULE__
29+
|> :ets.tab2list()
30+
|> Enum.map(fn {_, project} -> project end)
31+
end
32+
33+
def add_projects(new_projects) when is_list(new_projects) do
34+
for new_project <- new_projects do
35+
# We use `:ets.insert_new/2` to avoid overwriting the cached project's entropy
36+
:ets.insert_new(__MODULE__, {new_project.root_uri, new_project})
37+
end
38+
end
39+
40+
def remove_projects(removed_projects) when is_list(removed_projects) do
41+
for removed_project <- removed_projects do
42+
:ets.delete(__MODULE__, removed_project.root_uri)
43+
end
44+
end
45+
46+
def set_projects(new_projects) when is_list(new_projects) do
47+
:ets.delete_all_objects(__MODULE__)
48+
add_projects(new_projects)
49+
end
50+
end

apps/expert/lib/expert/application.ex

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ defmodule Expert.Application do
1818
{GenLSP.Assigns, [name: Expert.Assigns]},
1919
{Task.Supervisor, name: :expert_task_queue},
2020
{GenLSP.Buffer, name: Expert.Buffer},
21+
{Expert.ActiveProjects, []},
2122
{Expert,
2223
buffer: Expert.Buffer,
2324
task_supervisor: :expert_task_queue,

apps/expert/lib/expert/configuration.ex

Lines changed: 4 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -5,20 +5,17 @@ defmodule Expert.Configuration do
55

66
alias Expert.Configuration.Support
77
alias Expert.Dialyzer
8-
alias Forge.Project
98
alias Forge.Protocol.Id
109
alias GenLSP.Notifications.WorkspaceDidChangeConfiguration
1110
alias GenLSP.Requests
1211
alias GenLSP.Structures
1312

14-
defstruct projects: [],
15-
support: nil,
13+
defstruct support: nil,
1614
client_name: nil,
1715
additional_watched_extensions: nil,
1816
dialyzer_enabled?: false
1917

2018
@type t :: %__MODULE__{
21-
projects: [Project.t()],
2219
support: support | nil,
2320
client_name: String.t() | nil,
2421
additional_watched_extensions: [String.t()] | nil,
@@ -29,16 +26,11 @@ defmodule Expert.Configuration do
2926

3027
@dialyzer {:nowarn_function, set_dialyzer_enabled: 2}
3128

32-
@spec new([Structures.WorkspaceFolder.t()], map(), String.t() | nil) :: t
33-
def new(workspace_folders, %Structures.ClientCapabilities{} = client_capabilities, client_name) do
29+
@spec new(map(), String.t() | nil) :: t
30+
def new(%Structures.ClientCapabilities{} = client_capabilities, client_name) do
3431
support = Support.new(client_capabilities)
3532

36-
projects =
37-
for %{uri: uri} <- workspace_folders do
38-
Project.new(uri)
39-
end
40-
41-
%__MODULE__{support: support, projects: projects, client_name: client_name}
33+
%__MODULE__{support: support, client_name: client_name}
4234
|> tap(&set/1)
4335
end
4436

@@ -147,30 +139,4 @@ defmodule Expert.Configuration do
147139
defp maybe_add_watched_extensions(%__MODULE__{} = old_config, _) do
148140
{:ok, old_config}
149141
end
150-
151-
@spec add_projects(t, [Project.t()]) :: t
152-
def add_projects(%__MODULE__{} = config, projects) do
153-
new_config =
154-
for project <- projects, reduce: config do
155-
config ->
156-
if Enum.any?(config.projects, &(&1.root_uri == project.root_uri)) do
157-
config
158-
else
159-
projects = [project | config.projects]
160-
%__MODULE__{config | projects: projects}
161-
end
162-
end
163-
164-
set(new_config)
165-
166-
new_config
167-
end
168-
169-
@spec remove_projects(t, [String.t()]) :: t
170-
def remove_projects(%__MODULE__{} = config, projects) do
171-
new_projects = Enum.reject(config.projects, &(&1.root_uri in projects))
172-
new_config = %__MODULE__{config | projects: new_projects}
173-
set(new_config)
174-
new_config
175-
end
176142
end

apps/expert/lib/expert/provider/handlers/code_action.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.CodeAction do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Forge.CodeAction
@@ -8,10 +9,11 @@ defmodule Expert.Provider.Handlers.CodeAction do
89

910
def handle(
1011
%Requests.TextDocumentCodeAction{params: %Structures.CodeActionParams{} = params},
11-
%Configuration{} = config
12+
%Configuration{}
1213
) do
1314
document = Forge.Document.Container.context_document(params, nil)
14-
project = Project.project_for_document(config.projects, document)
15+
projects = ActiveProjects.projects()
16+
project = Project.project_for_document(projects, document)
1517
diagnostics = Enum.map(params.context.diagnostics, &to_code_action_diagnostic/1)
1618

1719
code_actions =

apps/expert/lib/expert/provider/handlers/code_lens.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.CodeLens do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Expert.Provider.Handlers
@@ -14,10 +15,11 @@ defmodule Expert.Provider.Handlers.CodeLens do
1415

1516
def handle(
1617
%Requests.TextDocumentCodeLens{params: %Structures.CodeLensParams{} = params},
17-
%Configuration{} = config
18+
%Configuration{}
1819
) do
1920
document = Document.Container.context_document(params, nil)
20-
project = Project.project_for_document(config.projects, document)
21+
projects = ActiveProjects.projects()
22+
project = Project.project_for_document(projects, document)
2123

2224
document = Document.Container.context_document(params, nil)
2325

apps/expert/lib/expert/provider/handlers/commands.ex

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.Commands do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Forge.Project
@@ -25,14 +26,16 @@ defmodule Expert.Provider.Handlers.Commands do
2526

2627
def handle(
2728
%Requests.WorkspaceExecuteCommand{params: %Structures.ExecuteCommandParams{} = params},
28-
%Configuration{} = config
29+
%Configuration{}
2930
) do
31+
projects = ActiveProjects.projects()
32+
3033
response =
3134
case params.command do
3235
@reindex_name ->
33-
project_names = Enum.map_join(config.projects, ", ", &Project.name/1)
36+
project_names = Enum.map_join(projects, ", ", &Project.name/1)
3437
Logger.info("Reindex #{project_names}")
35-
reindex_all(config.projects)
38+
reindex_all(projects)
3639

3740
invalid ->
3841
message = "#{invalid} is not a valid command"

apps/expert/lib/expert/provider/handlers/completion.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.Completion do
2+
alias Expert.ActiveProjects
23
alias Expert.CodeIntelligence
34
alias Expert.Configuration
45
alias Forge.Ast
@@ -14,10 +15,11 @@ defmodule Expert.Provider.Handlers.Completion do
1415
%Requests.TextDocumentCompletion{
1516
params: %Structures.CompletionParams{} = params
1617
},
17-
%Configuration{} = config
18+
%Configuration{}
1819
) do
1920
document = Document.Container.context_document(params, nil)
20-
project = Project.project_for_document(config.projects, document)
21+
projects = ActiveProjects.projects()
22+
project = Project.project_for_document(projects, document)
2123

2224
completions =
2325
CodeIntelligence.Completion.complete(

apps/expert/lib/expert/provider/handlers/document_symbols.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.DocumentSymbols do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Forge.CodeIntelligence.Symbols
@@ -8,9 +9,10 @@ defmodule Expert.Provider.Handlers.DocumentSymbols do
89
alias GenLSP.Requests
910
alias GenLSP.Structures
1011

11-
def handle(%Requests.TextDocumentDocumentSymbol{} = request, %Configuration{} = config) do
12+
def handle(%Requests.TextDocumentDocumentSymbol{} = request, %Configuration{}) do
1213
document = Document.Container.context_document(request.params, nil)
13-
project = Project.project_for_document(config.projects, document)
14+
projects = ActiveProjects.projects()
15+
project = Project.project_for_document(projects, document)
1416

1517
symbols =
1618
project

apps/expert/lib/expert/provider/handlers/find_references.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.FindReferences do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Forge.Ast
@@ -11,10 +12,11 @@ defmodule Expert.Provider.Handlers.FindReferences do
1112

1213
def handle(
1314
%TextDocumentReferences{params: %Structures.ReferenceParams{} = params},
14-
%Configuration{} = config
15+
%Configuration{}
1516
) do
1617
document = Forge.Document.Container.context_document(params, nil)
17-
project = Project.project_for_document(config.projects, document)
18+
projects = ActiveProjects.projects()
19+
project = Project.project_for_document(projects, document)
1820
include_declaration? = !!params.context.include_declaration
1921

2022
locations =

apps/expert/lib/expert/provider/handlers/formatting.ex

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
defmodule Expert.Provider.Handlers.Formatting do
2+
alias Expert.ActiveProjects
23
alias Expert.Configuration
34
alias Expert.EngineApi
45
alias Forge.Document.Changes
@@ -10,10 +11,11 @@ defmodule Expert.Provider.Handlers.Formatting do
1011

1112
def handle(
1213
%Requests.TextDocumentFormatting{params: %Structures.DocumentFormattingParams{} = params},
13-
%Configuration{} = config
14+
%Configuration{}
1415
) do
1516
document = Forge.Document.Container.context_document(params, nil)
16-
project = Project.project_for_document(config.projects, document)
17+
projects = ActiveProjects.projects()
18+
project = Project.project_for_document(projects, document)
1719

1820
case EngineApi.format(project, document) do
1921
{:ok, %Changes{} = document_edits} ->

0 commit comments

Comments
 (0)