Skip to content

Commit 4725400

Browse files
committed
feat: inspector
1 parent eaa8062 commit 4725400

25 files changed

+788
-20
lines changed

.formatter.exs

+1-1
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
request: 2
99
],
1010
line_length: 120,
11-
import_deps: [:gen_lsp],
11+
import_deps: [:gen_lsp, :plug, :temple],
1212
plugins: [Styler],
1313
inputs: [
1414
".formatter.exs",

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -30,3 +30,5 @@ result
3030
# Ignore dialyzer plt files
3131
/priv/plts/*.plt
3232
/priv/plts/*.plt.hash
33+
34+
/priv/css/*

assets/css/app.css

+14
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
@import "tailwindcss/base";
2+
@import "tailwindcss/components";
3+
@import "tailwindcss/utilities";
4+
5+
@import url('https://fonts.googleapis.com/css2?family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
6+
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&family=Rubik:ital,wght@0,300;0,400;0,500;0,600;0,700;0,800;0,900;1,300;1,400;1,500;1,600;1,700;1,800;1,900&display=swap');
7+
8+
pre {
9+
@apply bg-zinc-500 text-white rounded px-2 text-sm;
10+
}
11+
12+
h1, h2, h3, h4, h5 {
13+
@apply font-fancy font-semibold;
14+
}

assets/tailwind.config.js

+17
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
// See the Tailwind configuration guide for advanced usage
2+
// https://tailwindcss.com/docs/configuration
3+
4+
const defaultTheme = require("tailwindcss/defaultTheme");
5+
6+
module.exports = {
7+
content: ["./js/**/*.js", "./lib/**/*.ex"],
8+
theme: {
9+
extend: {
10+
fontFamily: {
11+
sans: ['"Inter"', ...defaultTheme.fontFamily.sans],
12+
fancy: ['"Rubik"', ...defaultTheme.fontFamily.sans],
13+
},
14+
},
15+
},
16+
plugins: [],
17+
};

bin/start

+1-1
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,4 @@
22

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

5-
elixir -S mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"
5+
mix run --no-halt -e "Application.ensure_all_started(:next_ls)" -- "$@"

config/config.exs

+35-1
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,40 @@ import Config
22

33
config :next_ls, :indexing_timeout, 100
44

5-
config :logger, :default_handler, config: [type: :standard_error]
5+
config :temple,
6+
engine: EEx.SmartEngine,
7+
attributes: {Temple, :attributes}
8+
9+
config :tailwind,
10+
version: "3.3.2",
11+
default: [
12+
args: ~w(
13+
--config=assets/tailwind.config.js
14+
--input=assets/css/app.css
15+
--output=priv/css/site.css
16+
)
17+
]
18+
19+
# config :logger, :default_handler, config: [type: :standard_error]
20+
config :logger, :default_handler,
21+
config: [
22+
file: ~c".elixir-tools/next-ls.log",
23+
filesync_repeat_interval: 5000,
24+
file_check: 5000,
25+
max_no_bytes: 10_000_000,
26+
max_no_files: 5,
27+
compress_on_rotate: true
28+
]
29+
30+
config :next_ls, :logger, [
31+
{:handler, :ui_logger, NextLS.UI.Logger,
32+
%{
33+
config: %{},
34+
formatter: Logger.Formatter.new()
35+
}}
36+
]
37+
38+
config :logger, :default_formatter,
39+
format: "\n$time $metadata[$level] $message\n"
640

741
import_config "#{config_env()}.exs"

config/dev.exs

+2
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
11
import Config
2+
3+
config :next_ls, :assets, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}

lib/next_ls.ex

+26-1
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,12 @@ defmodule NextLS do
7171
cache = Keyword.fetch!(args, :cache)
7272
{:ok, logger} = DynamicSupervisor.start_child(dynamic_supervisor, {NextLS.Logger, lsp: lsp})
7373

74+
{:ok, ui} =
75+
DynamicSupervisor.start_child(
76+
dynamic_supervisor,
77+
{Bandit, [plug: NextLS.UI.Router, port: "NEXTLS_UI_PORT" |> System.get_env("0") |> String.to_integer()]}
78+
)
79+
7480
{:ok,
7581
assign(lsp,
7682
auto_update: Keyword.get(args, :auto_update, false),
@@ -85,7 +91,8 @@ defmodule NextLS do
8591
registry: registry,
8692
extensions: extensions,
8793
ready: false,
88-
client_capabilities: nil
94+
client_capabilities: nil,
95+
ui: ui
8996
)}
9097
end
9198

@@ -127,6 +134,7 @@ defmodule NextLS do
127134
nil
128135
end,
129136
document_formatting_provider: true,
137+
execute_command_provider: %GenLSP.Structures.ExecuteCommandOptions{commands: ["open-ui"]},
130138
hover_provider: true,
131139
workspace_symbol_provider: true,
132140
document_symbol_provider: true,
@@ -572,6 +580,23 @@ defmodule NextLS do
572580
{:reply, [], lsp}
573581
end
574582

583+
def handle_request(
584+
%GenLSP.Requests.WorkspaceExecuteCommand{params: %GenLSP.Structures.ExecuteCommandParams{command: command}},
585+
lsp
586+
) do
587+
{:ok, {_, port}} = ThousandIsland.listener_info(lsp.assigns.ui)
588+
589+
case command do
590+
"open-ui" ->
591+
System.cmd("open", ["http://localhost:#{port}"])
592+
593+
_ ->
594+
NextLS.Logger.warning(lsp.logger, "[Next LS] Unknown workspace command: #{command}")
595+
end
596+
597+
{:reply, nil, lsp}
598+
end
599+
575600
def handle_request(%Shutdown{}, lsp) do
576601
{:reply, nil, assign(lsp, exit_code: 0)}
577602
end

lib/next_ls/application.ex

+127-2
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,116 @@
1+
defmodule NextLS.OpentelemetrySchematic do
2+
@moduledoc false
3+
require Logger
4+
5+
@tracer_id __MODULE__
6+
7+
def setup do
8+
:ok =
9+
:telemetry.attach_many(
10+
"schematic-handler",
11+
[
12+
[:schematic, :unify, :start],
13+
[:schematic, :unify, :stop]
14+
],
15+
&__MODULE__.process/4,
16+
nil
17+
)
18+
end
19+
20+
def process([:schematic, :unify, :start], _measurements, metadata, _config) do
21+
OpentelemetryTelemetry.start_telemetry_span(
22+
@tracer_id,
23+
:"schematic.unify.#{metadata.kind} #{metadata.dir}",
24+
metadata,
25+
%{kind: :server, attributes: metadata}
26+
)
27+
end
28+
29+
def process([:schematic, :unify, :stop], _measurements, metadata, _config) do
30+
OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
31+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
32+
end
33+
end
34+
35+
defmodule NextLS.OpentelemetryGenLSP do
36+
@moduledoc false
37+
require Logger
38+
require OpenTelemetry.Tracer, as: Tracer
39+
40+
@tracer_id __MODULE__
41+
42+
def setup do
43+
:ok =
44+
:telemetry.attach_many(
45+
"gen-lsp-handler",
46+
[
47+
[:gen_lsp, :loop, :start],
48+
[:gen_lsp, :loop, :stop],
49+
[:gen_lsp, :notification, :emit],
50+
[:gen_lsp, :info, :start],
51+
[:gen_lsp, :info, :stop],
52+
[:gen_lsp, :buffer, :read],
53+
[:gen_lsp, :buffer, :write]
54+
],
55+
&__MODULE__.process/4,
56+
nil
57+
)
58+
end
59+
60+
def process([:gen_lsp, :buffer, :read], _measurements, metadata, _config) do
61+
OpenTelemetry.Ctx.clear()
62+
63+
OpentelemetryTelemetry.start_telemetry_span(@tracer_id, :"gen_lsp.read", metadata, %{
64+
kind: :server,
65+
attributes: metadata
66+
})
67+
end
68+
69+
def process([:gen_lsp, :loop, :start], _measurements, metadata, _config) do
70+
parent_context = OpentelemetryProcessPropagator.fetch_parent_ctx(1, :"$callers")
71+
72+
if parent_context != :undefined do
73+
OpenTelemetry.Ctx.attach(parent_context)
74+
end
75+
76+
Tracer.update_name(:"gen_lsp.receive.#{metadata.type} #{metadata.method}")
77+
end
78+
79+
def process([:gen_lsp, :loop, :stop], _measurements, %{type: :request, reply: true} = _metadata, _config) do
80+
OpenTelemetry.Ctx.clear()
81+
end
82+
83+
def process([:gen_lsp, :loop, :stop], _measurements, metadata, _config) do
84+
OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
85+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
86+
OpenTelemetry.Ctx.clear()
87+
end
88+
89+
def process([:gen_lsp, :notification, :emit], _measurements, metadata, _config) do
90+
OpentelemetryTelemetry.start_telemetry_span(
91+
@tracer_id,
92+
:"gen_lsp.send.notification #{metadata.method}",
93+
metadata,
94+
%{
95+
kind: :server,
96+
attributes: metadata
97+
}
98+
)
99+
end
100+
101+
def process([:gen_lsp, :buffer, :write], _measurements, metadata, _config) do
102+
parent_context = OpentelemetryProcessPropagator.fetch_parent_ctx(1, :"$callers")
103+
104+
if parent_context != :undefined do
105+
OpenTelemetry.Ctx.attach(parent_context)
106+
end
107+
108+
OpentelemetryTelemetry.set_current_telemetry_span(@tracer_id, metadata)
109+
OpentelemetryTelemetry.end_telemetry_span(@tracer_id, metadata)
110+
OpenTelemetry.Ctx.clear()
111+
end
112+
end
113+
1114
defmodule NextLS.Application do
2115
# See https://hexdocs.pm/elixir/Application.html
3116
# for more information on OTP Applications
@@ -7,6 +120,8 @@ defmodule NextLS.Application do
7120

8121
@impl true
9122
def start(_type, _args) do
123+
Logger.add_handlers(:next_ls)
124+
10125
case System.cmd("epmd", ["-daemon"], stderr_to_stdout: true) do
11126
{_, 0} ->
12127
:ok
@@ -19,11 +134,21 @@ defmodule NextLS.Application do
19134

20135
Node.start(:"next-ls-#{System.system_time()}", :shortnames)
21136

22-
children = [NextLS.LSPSupervisor]
137+
children = [
138+
NextLS.UI.CodeReloader,
139+
{Registry, name: NextLS.UI.Registry, keys: :duplicate},
140+
NextLS.LSPSupervisor
141+
]
23142

24143
# See https://hexdocs.pm/elixir/Supervisor.html
25144
# for other strategies and supported options
26145
opts = [strategy: :one_for_one, name: NextLS.Supervisor]
27-
Supervisor.start_link(children, opts)
146+
Supervisor.start_link(children ++ asset_children(), opts)
147+
end
148+
149+
def asset_children do
150+
for conf <- Application.get_env(:next_ls, :assets, []) do
151+
{NextLS.UI.Assets, conf}
152+
end
28153
end
29154
end

lib/next_ls/db.ex

+4-1
Original file line numberDiff line numberDiff line change
@@ -192,7 +192,10 @@ defmodule NextLS.DB do
192192
def __query__({conn, logger}, query, args) do
193193
args = Enum.map(args, &cast/1)
194194

195-
case Exqlite.Basic.exec(conn, query, args) do
195+
{duration, result} = :timer.tc(fn -> Exqlite.Basic.exec(conn, query, args) end, :millisecond)
196+
IO.inspect("#{duration}ms")
197+
198+
case result do
196199
{:error, %{message: message, statement: statement}, _} ->
197200
NextLS.Logger.warning(logger, """
198201
sqlite3 error: #{message}

lib/next_ls/db/activity.ex

+8-1
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,14 @@ defmodule NextLS.DB.Activity do
1313
:gen_statem.start_link({:local, Keyword.get(args, :name)}, __MODULE__, Keyword.drop(args, [:name]), [])
1414
end
1515

16-
def update(statem, count), do: :gen_statem.cast(statem, count)
16+
def update(statem, count) do
17+
Registry.dispatch(NextLS.UI.Registry, :activity_socket, fn entries ->
18+
for {pid, _} <- entries, do: send(pid, {:activity, count})
19+
20+
end)
21+
22+
:gen_statem.cast(statem, count)
23+
end
1724

1825
@impl :gen_statem
1926
def callback_mode, do: :state_functions

lib/next_ls/logger.ex

+10
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ defmodule NextLS.Logger do
22
@moduledoc false
33
use GenServer
44

5+
require Logger
6+
57
def start_link(arg) do
68
GenServer.start_link(__MODULE__, arg, Keyword.take(arg, [:name]))
79
end
@@ -22,6 +24,14 @@ defmodule NextLS.Logger do
2224

2325
def handle_cast({:log, type, msg}, state) do
2426
apply(GenLSP, type, [state.lsp, String.trim("[NextLS] #{msg}")])
27+
28+
case type do
29+
:log -> Logger.debug(msg)
30+
:warning -> Logger.warning(msg)
31+
:error -> Logger.error(msg)
32+
:info -> Logger.info(msg)
33+
end
34+
2535
{:noreply, state}
2636
end
2737

0 commit comments

Comments
 (0)