Skip to content

Commit fe9f9d0

Browse files
committed
feat: inspector
1 parent e871f34 commit fe9f9d0

20 files changed

+780
-9
lines changed

.formatter.exs

Lines changed: 1 addition & 1 deletion
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",

assets/css/app.css

Lines changed: 14 additions & 0 deletions
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

Lines changed: 17 additions & 0 deletions
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+
};

config/config.exs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,21 @@ import Config
22

33
config :next_ls, :indexing_timeout, 100
44

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]
520
config :logger, :default_handler,
621
config: [
722
file: ~c".elixir-tools/next-ls.log",
@@ -14,4 +29,12 @@ config :logger, :default_handler,
1429

1530
config :logger, :default_formatter, format: "\n$time $metadata[$level] $message\n", metadata: [:id]
1631

32+
config :next_ls, :logger, [
33+
{:handler, :ui_logger, NextLS.UI.Logger,
34+
%{
35+
config: %{},
36+
formatter: Logger.Formatter.new()
37+
}}
38+
]
39+
1740
import_config "#{config_env()}.exs"

config/dev.exs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1 +1,6 @@
11
import Config
2+
3+
config :next_ls, :assets, tailwind: {Tailwind, :install_and_run, [:default, ~w(--watch)]}
4+
5+
config :web_dev_utils, :reload_url, "'wss://' + location.host + '/ws'"
6+
config :web_dev_utils, :reload_log, true

lib/next_ls.ex

Lines changed: 31 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -64,13 +64,22 @@ defmodule NextLS do
6464
task_supervisor = Keyword.fetch!(args, :task_supervisor)
6565
runtime_task_supervisor = Keyword.fetch!(args, :runtime_task_supervisor)
6666
dynamic_supervisor = Keyword.fetch!(args, :dynamic_supervisor)
67-
6867
registry = Keyword.fetch!(args, :registry)
69-
7068
extensions = Keyword.get(args, :extensions, elixir: NextLS.ElixirExtension, credo: NextLS.CredoExtension)
7169
cache = Keyword.fetch!(args, :cache)
70+
7271
{:ok, logger} = DynamicSupervisor.start_child(dynamic_supervisor, {NextLS.Logger, lsp: lsp})
7372

73+
{:ok, ui} =
74+
DynamicSupervisor.start_child(
75+
dynamic_supervisor,
76+
{Bandit,
77+
[
78+
plug: {NextLS.UI.Router, registry: registry},
79+
port: "NEXTLS_UI_PORT" |> System.get_env("0") |> String.to_integer()
80+
]}
81+
)
82+
7483
{:ok,
7584
assign(lsp,
7685
auto_update: Keyword.get(args, :auto_update, false),
@@ -85,7 +94,8 @@ defmodule NextLS do
8594
registry: registry,
8695
extensions: extensions,
8796
ready: false,
88-
client_capabilities: nil
97+
client_capabilities: nil,
98+
ui: ui
8999
)}
90100
end
91101

@@ -127,6 +137,7 @@ defmodule NextLS do
127137
nil
128138
end,
129139
document_formatting_provider: true,
140+
execute_command_provider: %GenLSP.Structures.ExecuteCommandOptions{commands: ["open-ui"]},
130141
hover_provider: true,
131142
workspace_symbol_provider: true,
132143
document_symbol_provider: true,
@@ -572,6 +583,23 @@ defmodule NextLS do
572583
{:reply, [], lsp}
573584
end
574585

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

lib/next_ls/application.ex

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,22 @@ defmodule NextLS.Application do
2424

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

27-
children = [NextLS.LSPSupervisor]
27+
children = [
28+
{Registry, name: NextLS.UI.Registry, keys: :duplicate},
29+
WebDevUtils.FileSystem,
30+
WebDevUtils.CodeReloader,
31+
NextLS.LSPSupervisor
32+
]
2833

2934
# See https://hexdocs.pm/elixir/Supervisor.html
3035
# for other strategies and supported options
3136
opts = [strategy: :one_for_one, name: NextLS.Supervisor]
32-
Supervisor.start_link(children, opts)
37+
Supervisor.start_link(children ++ asset_children(), opts)
38+
end
39+
40+
def asset_children do
41+
for conf <- Application.get_env(:next_ls, :assets, []) do
42+
{WebDevUtils.Assets, conf}
43+
end
3344
end
3445
end

lib/next_ls/db/activity.ex

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,13 @@ 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, time \\ DateTime.utc_now() |> DateTime.to_unix(:millisecond)) do
17+
Registry.dispatch(NextLS.UI.Registry, :activity_socket, fn entries ->
18+
for {pid, _} <- entries, do: send(pid, {:activity, count, time})
19+
end)
20+
21+
:gen_statem.cast(statem, count)
22+
end
1723

1824
@impl :gen_statem
1925
def callback_mode, do: :state_functions
@@ -44,6 +50,10 @@ defmodule NextLS.DB.Activity do
4450
{:next_state, :waiting, %{data | token: nil}}
4551
end
4652

53+
# def active(event, msg, data) do
54+
# handle_event(event, msg, data)
55+
# end
56+
4757
def waiting(:cast, 0, _data) do
4858
:keep_state_and_data
4959
end
@@ -53,4 +63,12 @@ defmodule NextLS.DB.Activity do
5363
NextLS.Progress.start(data.lsp, token, "Indexing!")
5464
{:next_state, :active, %{data | count: mailbox_count, token: token}}
5565
end
66+
67+
# def waiting(event, msg, data) do
68+
# handle_event(event, msg, data)
69+
# end
70+
71+
# defp handle_event({:call, from}, :get, data) do
72+
# {:keep_state, data, [{:reply, from, data.count}]}
73+
# end
5674
end

lib/next_ls/runtime/supervisor.ex

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,9 @@ defmodule NextLS.Runtime.Supervisor do
2626
children = [
2727
{NextLS.Runtime.Sidecar, name: sidecar_name, db: db_name},
2828
{NextLS.DB.Activity,
29-
logger: logger, name: db_activity, lsp: lsp, timeout: Application.get_env(:next_ls, :indexing_timeout)},
29+
logger: logger,
30+
registry: registry,
31+
name: db_activity, lsp: lsp, timeout: Application.get_env(:next_ls, :indexing_timeout)},
3032
{NextLS.DB,
3133
logger: logger,
3234
file: "#{hidden_folder}/nextls.db",

lib/next_ls_ui/components.ex

Lines changed: 111 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,111 @@
1+
defmodule NextLS.UI.Component do
2+
@moduledoc false
3+
use Temple.Component
4+
5+
defmacro __using__(_) do
6+
quote do
7+
import Temple
8+
import unquote(__MODULE__)
9+
end
10+
end
11+
end
12+
13+
defmodule NextLS.UI.Components do
14+
@moduledoc false
15+
use NextLS.UI.Component
16+
17+
@env Mix.env()
18+
19+
def root(assigns) do
20+
assigns = Map.put(assigns, :env, @env)
21+
22+
temple do
23+
"<!DOCTYPE html>"
24+
25+
html lang: "en" do
26+
head do
27+
meta charset: "utf-8"
28+
meta http_equiv: "X-UA-Compatible", content: "IE=edge"
29+
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
30+
31+
title do
32+
"Next LS Inspector"
33+
end
34+
35+
script src: "https://unpkg.com/[email protected]"
36+
script src: "https://unpkg.com/htmx.org/dist/ext/ws.js"
37+
script src: "https://unpkg.com/htmx.org/dist/ext/morphdom-swap.js"
38+
39+
script src: "https://cdnjs.cloudflare.com/ajax/libs/Chart.js/4.4.0/chart.umd.min.js"
40+
41+
script src: "https://cdn.jsdelivr.net/npm/luxon@^2"
42+
script src: "https://cdn.jsdelivr.net/npm/chartjs-adapter-luxon@^1"
43+
44+
45+
link rel: "stylesheet", href: "/css/site.css"
46+
end
47+
48+
body class: "bg-zinc-200 dark:bg-zinc-900 font-sans", hx_ext: "morphdom-swap" do
49+
main class: "container mx-auto" do
50+
header class: "mb-8 py-2" do
51+
div class: "flex items-center space-x-2" do
52+
a href: "/", class: "hover:underline" do
53+
img src: "/nextls-logo-no-background.png", class: "h-8 w-8"
54+
end
55+
56+
h2 class: "text-xl dark:text-white" do
57+
a href: "/" do
58+
"Next LS Inspector"
59+
end
60+
end
61+
end
62+
end
63+
64+
slot @inner_block
65+
66+
footer class: "flex justify-between dark:text-white mt-8 py-4" do
67+
div do
68+
a class: "underline",
69+
href: "https://github.com/elixir-tools/next-ls",
70+
do: "Source Code"
71+
end
72+
73+
div class: "italic" do
74+
span do: "Built with"
75+
76+
a class: "underline",
77+
href: "https://github.com/mhanberg/temple",
78+
do: "Temple,"
79+
80+
a class: "underline",
81+
href: "https://tailwindcss.com",
82+
do: "TailwindCSS,"
83+
84+
a class: "underline",
85+
href: "https://htmx.org",
86+
do: "HTMX,"
87+
88+
" and"
89+
90+
span class: "text-red-500", do: "♥"
91+
end
92+
end
93+
end
94+
95+
if @env == :dev do
96+
c &WebDevUtils.Components.live_reload/1
97+
end
98+
end
99+
end
100+
end
101+
end
102+
103+
def card(assigns) do
104+
temple do
105+
div class: "#{assigns[:class]} bg-zinc-50 dark:bg-zinc-700 dark:text-white rounded shadow-xl p-2",
106+
rest!: Map.take(assigns, [:id]) do
107+
slot @inner_block
108+
end
109+
end
110+
end
111+
end

lib/next_ls_ui/logger.ex

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
defmodule NextLS.UI.Logger do
2+
@moduledoc false
3+
def log(event, _config) do
4+
if Process.alive?(Process.whereis(NextLS.UI.Registry)) do
5+
Registry.dispatch(NextLS.UI.Registry, :log_socket, fn entries ->
6+
for {pid, _} <- entries do
7+
send(pid, {:log, event})
8+
end
9+
end)
10+
end
11+
end
12+
end

0 commit comments

Comments
 (0)