@@ -5,19 +5,35 @@ defmodule LiveDebugger.App.Debugger.Web.LiveComponents.NodeBasicInfo do
55
66 use LiveDebugger.App.Web , :live_component
77
8+ import LiveDebugger.App.Web.Hooks.Flash , only: [ push_flash: 3 ]
89 alias LiveDebugger.App.Debugger.Structs.TreeNode
910 alias LiveDebugger.App.Debugger.Queries.Node , as: NodeQueries
1011 alias LiveDebugger.App.Debugger.Web.LiveComponents.SendEventFullscreen
1112 alias LiveDebugger.App.Utils.Parsers
12-
1313 alias LiveDebugger.App.Debugger.Web.Components.Pages
14+ alias LiveDebugger.App.Debugger.Utils.Editor
15+ alias LiveDebugger.App.Web.Hooks.Flash.LinkFlashData
16+
17+ @ editor_docs_url "https://hexdocs.pm/live_debugger/open_in_editor.html"
1418
1519 @ impl true
20+ def update ( % { :editor_error => editor_error } , socket ) do
21+ socket
22+ |> push_flash ( :error , % LinkFlashData {
23+ text: editor_error ,
24+ url: @ editor_docs_url ,
25+ label: "See the docs"
26+ } )
27+ |> ok ( )
28+ end
29+
1630 def update ( assigns , socket ) do
1731 socket
1832 |> assign ( :id , assigns . id )
1933 |> assign ( :node_id , assigns . node_id )
2034 |> assign ( :lv_process , assigns . lv_process )
35+ |> assign ( :elixir_editor , Editor . detect_editor ( ) )
36+ |> assign ( :editor_docs_url , @ editor_docs_url )
2137 |> assign_node_type ( )
2238 |> assign_async_node_module ( )
2339 |> ok ( )
@@ -43,17 +59,85 @@ defmodule LiveDebugger.App.Debugger.Web.LiveComponents.NodeBasicInfo do
4359 < p > Couldn't load basic information about the node.</ p >
4460 </ . alert >
4561 </: failed >
46- < div class = "flex flex-row gap-8 max-md_ct :flex-col max-md_ct :gap-2 md_ct :items-center p-3 " >
47- < div class = "min-w-0 flex flex-col gap-2 max-md_ct:border-b max-md_ct:border-default-border " >
62+ < div class = "flex flex-row gap-8 max-sm_bi :flex-col max-sm_bi :gap-4 sm_bi :items-center p-3 " >
63+ < div class = "min-w-0 flex flex-col gap-2 " >
4864 < span class = "font-medium " > Module:</ span >
4965 < div class = "flex gap-2 min-w-0 " >
50- < . tooltip id = { @ id <> "-current-node-module" } content = { node_module } class = "truncate " >
51- <%= node_module %>
66+ < . tooltip
67+ id = { @ id <> "-current-node-module" }
68+ content = { node_module . module_name }
69+ class = "truncate "
70+ >
71+ <%= node_module . module_name %>
72+ </ . tooltip >
73+ < . copy_button id = "copy-button-module-name " value = { node_module . module_name } />
74+ </ div >
75+ </ div >
76+
77+ < div class = "min-w-0 flex flex-col gap-2 " >
78+ < span class = "font-medium " > Path:</ span >
79+
80+ < div class = "flex flex-row gap-2 " >
81+ < . tooltip
82+ id = { @ id <> "-current-node-module-path" }
83+ content = { node_module . module_path <> ":" <> Integer . to_string ( node_module . line ) }
84+ class = "truncate "
85+ >
86+ <%= node_module . module_path <> ":" <> Integer . to_string ( node_module . line ) %>
5287 </ . tooltip >
53- < . copy_button id = "copy-button-module-name " value = { node_module } />
88+ < . copy_button id = "copy-button-module-path " value = { node_module . module_path } />
89+ </ div >
90+ </ div >
91+
92+ < div class = "shrink-0 flex flex-col gap-2 max-sm_bi:border-b max-sm_bi:border-default-border pb-2 " >
93+ < span class = "font-medium " > Type:</ span >
94+ < span > <%= @ node_type %> </ span >
95+ </ div >
96+
97+ < div class = "flex flex-row gap-2 max-sm_ct:flex-col sm_bi:ml-auto " >
98+ < div class = "flex flex-row gap-2 " >
99+ < . button
100+ class = "shrink-0 sm_bi:ml-auto "
101+ variant = "secondary "
102+ size = "sm "
103+ id = "send-event-button "
104+ disabled = { not @ lv_process . alive? }
105+ phx-click = "open-send-event "
106+ phx-target = { @ myself }
107+ >
108+ < . icon name = "icon-send " class = "w-4 h-4 " /> Send Event
109+ </ . button >
110+
111+ < div class = "flex flex-row items-center gap-2 " >
112+ < . button
113+ disabled = { ! @ elixir_editor }
114+ class = "shrink-0 "
115+ variant = "secondary "
116+ id = "open-in-editor "
117+ size = "sm "
118+ phx-click = "open-in-editor "
119+ phx-target = { @ myself }
120+ phx-value-file = { node_module . module_path }
121+ phx-value-line = { node_module . line }
122+ >
123+ < . icon name = "icon-external-link " class = "w-4 h-4 " /> Open in Editor
124+ </ . button >
125+
126+ < . tooltip
127+ id = { @ id <> "-env-not-set" }
128+ content = "Cannot open in editor? Click to see the documentation. "
129+ >
130+ < span :if = { ! @ elixir_editor } class = "text-error-text " >
131+ < . link href = { @ editor_docs_url } target = "_blank " >
132+ < . icon name = "icon-info " class = "w-4 h-4 " />
133+ </ . link >
134+ </ span >
135+ </ . tooltip >
136+ </ div >
54137 </ div >
138+
55139 < . button
56- class = "shrink-0 md_ct :ml-auto md_ct:hidden mb-3 "
140+ class = "shrink-0 sm_bi :ml-auto md_ct:hidden "
57141 variant = "secondary "
58142 size = "sm "
59143 id = "show-components-tree-button "
@@ -62,22 +146,6 @@ defmodule LiveDebugger.App.Debugger.Web.LiveComponents.NodeBasicInfo do
62146 < . icon name = "icon-component " class = "w-4 h-4 " /> Show Components Tree
63147 </ . button >
64148 </ div >
65- < div class = "shrink-0 flex flex-col gap-2 " >
66- < span class = "font-medium " > Type:</ span >
67- < span > <%= @ node_type %> </ span >
68- </ div >
69-
70- < . button
71- class = "shrink-0 md_ct:ml-auto "
72- variant = "secondary "
73- size = "sm "
74- id = "send-event-button "
75- disabled = { not @ lv_process . alive? }
76- phx-click = "open-send-event "
77- phx-target = { @ myself }
78- >
79- < . icon name = "icon-send " class = "w-4 h-4 " /> Send Event
80- </ . button >
81149 </ div >
82150 </ . async_result >
83151 < . live_component
@@ -97,6 +165,29 @@ defmodule LiveDebugger.App.Debugger.Web.LiveComponents.NodeBasicInfo do
97165 |> noreply ( )
98166 end
99167
168+ def handle_event ( "open-in-editor" , % { "file" => file , "line" => line } , socket ) do
169+ cmd = Editor . get_editor_cmd ( socket . assigns . elixir_editor , file , line |> String . to_integer ( ) )
170+
171+ # Some editors may block iex, so we spawn a new process
172+ component_id = socket . assigns . id
173+ component_pid = self ( )
174+
175+ spawn ( fn ->
176+ case Editor . run_shell_cmd ( cmd ) do
177+ :ok ->
178+ :ok
179+
180+ { :error , reason } ->
181+ send_update ( component_pid , __MODULE__ ,
182+ id: component_id ,
183+ editor_error: reason
184+ )
185+ end
186+ end )
187+
188+ { :noreply , socket }
189+ end
190+
100191 defp assign_node_type ( socket ) do
101192 node_type =
102193 socket . assigns . node_id
@@ -116,12 +207,34 @@ defmodule LiveDebugger.App.Debugger.Web.LiveComponents.NodeBasicInfo do
116207 assign_async ( socket , :node_module , fn ->
117208 case NodeQueries . get_module_from_id ( node_id , pid ) do
118209 { :ok , module } ->
119- node_module = Parsers . module_to_string ( module )
120- { :ok , % { node_module: node_module } }
210+ line = get_module_line ( module )
211+
212+ path = module . __info__ ( :compile ) |> Keyword . get ( :source ) |> List . to_string ( )
213+
214+ module_name = Parsers . module_to_string ( module )
215+
216+ { :ok ,
217+ % {
218+ node_module: % {
219+ module_name: module_name ,
220+ module_path: path ,
221+ line: line
222+ }
223+ } }
121224
122225 :error ->
123226 { :error , "Failed to get node module" }
124227 end
125228 end )
126229 end
230+
231+ defp get_module_line ( module ) do
232+ case Code . fetch_docs ( module ) do
233+ { :docs_v1 , _ , _ , _ , _ , % { source_annos: [ { line , _column } | _ ] } , _ } ->
234+ line
235+
236+ _ ->
237+ 1
238+ end
239+ end
127240end
0 commit comments