Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add "Copy to Markdown" button #1111

Merged

Conversation

dbernheisel
Copy link
Contributor

Added the code frames to the debugger Markdown template. This is borrowed from the HTML template but reformatted for markdown. Unfortunately required some arg drilling - if you have a suggestion on avoiding that I'll be happy to rework it.

image

Add Copy to markdown button
Screen Shot 2022-07-14 at 5 00 16 PM

When clicked it changes to a success message. Reverts to original in 5 seconds.
image

Video Demo
https://user-images.githubusercontent.com/643967/179085428-bf973028-748a-403c-8a0d-97c4a2b07dd4.mov

Sample Markdown output
# RuntimeError at GET /

Exception:

    ** (RuntimeError) boom
        (ejecto 0.1.0) lib/ejecto_web/live/home_live.ex:6: EjectoWeb.HomeLive.mount/3
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/utils.ex:301: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
        (telemetry 1.1.0) /home/dbern/ejecto/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:270: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:110: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.6.11) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
        (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.plug_builder_call/2
        (ejecto 0.1.0) lib/plug/debugger.ex:136: EjectoWeb.Endpoint."call (overridable 3)"/2
        (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.call/2
        (phoenix 1.6.11) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
        (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    

Code:

`lib/ejecto_web/live/home_live.ex`

    1   defmodule EjectoWeb.HomeLive do
    2     use EjectoWeb, :live_view
    3   
    4     def mount(_params, _session, socket) do
    5       if connected?(socket), do: {:ok, socket}
    6>      raise "boom"
    7       {:ok, socket}
    8     end
    9   
    10     def render(assigns) do
    11       ~H"""
    
`lib/phoenix_live_view/utils.ex`

    296           %{socket: socket, params: params, session: session, uri: uri},
    297           fn ->
    298             socket =
    299               case Lifecycle.mount(params, session, socket) do
    300                 {:cont, %Socket{} = socket} when exported? ->
    301>                  view.mount(params, session, socket)
    302   
    303                 {_, %Socket{} = socket} ->
    304                   {:ok, socket}
    305               end
    306               |> handle_mount_result!({:mount, 3, view})
    
`/home/dbern/ejecto/deps/telemetry/src/telemetry.erl`

    315           EventPrefix ++ [start],
    316           #{monotonic_time => StartTime, system_time => erlang:system_time()},
    317           merge_ctx(StartMetadata, DefaultCtx)
    318       ),
    319   
    320>      try {_, #{}} = SpanFunction() of
    321         {Result, StopMetadata} ->
    322             StopTime = erlang:monotonic_time(),
    323             execute(
    324                 EventPrefix ++ [stop],
    325                 #{duration => StopTime - StartTime, monotonic_time => StopTime},
    
`lib/phoenix_live_view/static.ex`

    265   
    266     defp call_mount_and_handle_params!(socket, view, session, params, uri) do
    267       mount_params = if socket.router, do: params, else: :not_mounted_at_router
    268   
    269       socket
    270>      |> Utils.maybe_call_live_view_mount!(view, mount_params, session, uri)
    271       |> mount_handle_params(view, params, uri)
    272       |> case do
    273         {:noreply, %Socket{redirected: {:live, _, _}} = socket} ->
    274           {:stop, socket}
    275   
    
`lib/phoenix_live_view/static.ex`

    105           action,
    106           flash,
    107           host_uri
    108         )
    109   
    110>      case call_mount_and_handle_params!(socket, view, mount_session, conn.params, request_url) do
    111         {:ok, socket} ->
    112           data_attrs = [
    113             phx_session: sign_root_session(socket, router, view, to_sign_session, live_session),
    114             phx_static: sign_static_token(socket)
    115           ]
    
`lib/phoenix_live_view/controller.ex`

    34           end
    35         end
    36   
    37     """
    38     def live_render(%Plug.Conn{} = conn, view, opts \\ []) do
    39>      case LiveView.Static.render(conn, view, opts) do
    40         {:ok, content, socket_assigns} ->
    41           conn
    42           |> Phoenix.Controller.put_view(LiveView.Static)
    43           |> Phoenix.Controller.render(
    44             "template.html",
    
`lib/phoenix/router.ex`

    349           metadata = %{metadata | conn: halted_conn}
    350           :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
    351           halted_conn
    352         %Plug.Conn{} = piped_conn ->
    353           try do
    354>            plug.call(piped_conn, plug.init(opts))
    355           else
    356             conn ->
    357               measurements = %{duration: System.monotonic_time() - start}
    358               metadata = %{metadata | conn: conn}
    359               :telemetry.execute([:phoenix, :router_dispatch, :stop], measurements, metadata)
    
`lib/ejecto_web/endpoint.ex`

    1>  defmodule EjectoWeb.Endpoint do
    2     use Phoenix.Endpoint, otp_app: :ejecto
    3   
    4     # The session will be stored in the cookie and signed,
    5     # this means its contents can be read but not tampered with.
    6     # Set :encryption_salt if you would also like to encrypt it.
    
`lib/plug/debugger.ex`

    No code available.

`lib/ejecto_web/endpoint.ex`

    1>  defmodule EjectoWeb.Endpoint do
    2     use Phoenix.Endpoint, otp_app: :ejecto
    3   
    4     # The session will be stored in the cookie and signed,
    5     # this means its contents can be read but not tampered with.
    6     # Set :encryption_salt if you would also like to encrypt it.
    
`lib/phoenix/endpoint/cowboy2_handler.ex`

    49             end
    50   
    51           {:plug, conn, handler, opts} ->
    52             %{adapter: {@connection, req}} =
    53               conn
    54>              |> handler.call(opts)
    55               |> maybe_send(handler)
    56   
    57             {:ok, req, {handler, opts}}
    58         end
    59       catch
    
`/home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl`

    32   -optional_callbacks([terminate/3]).
    33   
    34   -spec execute(Req, Env) -> {ok, Req, Env}
    35   	when Req::cowboy_req:req(), Env::cowboy_middleware:env().
    36   execute(Req, Env=#{handler := Handler, handler_opts := HandlerOpts}) ->
    37>  	try Handler:init(Req, HandlerOpts) of
    38   		{ok, Req2, State} ->
    39   			Result = terminate(normal, Req2, State, Handler),
    40   			{ok, Req2, Env#{result => Result}};
    41   		{Mod, Req2, State} ->
    42   			Mod:upgrade(Req2, Env, Handler, State);
    
`/home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl`

    301   	end.
    302   
    303   execute(_, _, []) ->
    304   	ok;
    305   execute(Req, Env, [Middleware|Tail]) ->
    306>  	case Middleware:execute(Req, Env) of
    307   		{ok, Req2, Env2} ->
    308   			execute(Req2, Env2, Tail);
    309   		{suspend, Module, Function, Args} ->
    310   			proc_lib:hibernate(?MODULE, resume, [Env, Tail, Module, Function, Args]);
    311   		{stop, _Req2} ->
    
`/home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl`

    290   %% to simplify the debugging of errors. The proc_lib library
    291   %% already adds the stacktrace to other types of exceptions.
    292   -spec request_process(cowboy_req:req(), cowboy_middleware:env(), [module()]) -> ok.
    293   request_process(Req, Env, Middlewares) ->
    294   	try
    295>  		execute(Req, Env, Middlewares)
    296   	catch
    297   		exit:Reason={shutdown, _}:Stacktrace ->
    298   			erlang:raise(exit, Reason, Stacktrace);
    299   		exit:Reason:Stacktrace when Reason =/= normal, Reason =/= shutdown ->
    300   			erlang:raise(exit, {Reason, Stacktrace}, Stacktrace)
    
`proc_lib.erl`

    No code available.


## Connection details

### Params

    %{}

### Request info

  * URI: http://192.168.86.59:4000/
  * Query string: 

### Headers
  
  * accept: text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9
  * accept-encoding: gzip, deflate
  * accept-language: en-US,en;q=0.9
  * cache-control: max-age=0
  * connection: keep-alive
  * cookie: _ejecto_key=SFMyNTY.g3QAAAABbQAAAAtfY3NyZl90b2tlbm0AAAAYc0d6MDl0czdUaUZWUHJKQjlNRWJONjJT.sKzUbyldKTGwL1CuSMjr7PWMzsVXjw6Jh13luyPzK-c
  * dnt: 1
  * host: 192.168.86.59:4000
  * referer: http://192.168.86.59:4000/
  * upgrade-insecure-requests: 1
  * user-agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/103.0.0.0 Safari/537.36

### Session

    %{"_csrf_token" => "sGz09ts7TiFVPrJB9MEbN62S"}
Example of bad match
# FunctionClauseError at GET /

Exception:

    ** (FunctionClauseError) no function clause matching in EjectoWeb.HomeLive.boom/2
        (ejecto 0.1.0) lib/ejecto_web/live/home_live.ex:10: EjectoWeb.HomeLive.boom(:fail, :foo)
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/utils.ex:301: anonymous fn/6 in Phoenix.LiveView.Utils.maybe_call_live_view_mount!/5
        (telemetry 1.1.0) /home/dbern/ejecto/deps/telemetry/src/telemetry.erl:320: :telemetry.span/3
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:270: Phoenix.LiveView.Static.call_mount_and_handle_params!/5
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/static.ex:110: Phoenix.LiveView.Static.render/3
        (phoenix_live_view 0.17.11) lib/phoenix_live_view/controller.ex:39: Phoenix.LiveView.Controller.live_render/3
        (phoenix 1.6.11) lib/phoenix/router.ex:354: Phoenix.Router.__call__/2
        (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.plug_builder_call/2
        (ejecto 0.1.0) lib/plug/debugger.ex:136: EjectoWeb.Endpoint."call (overridable 3)"/2
        (ejecto 0.1.0) lib/ejecto_web/endpoint.ex:1: EjectoWeb.Endpoint.call/2
        (phoenix 1.6.11) lib/phoenix/endpoint/cowboy2_handler.ex:54: Phoenix.Endpoint.Cowboy2Handler.init/4
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_handler.erl:37: :cowboy_handler.execute/2
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:306: :cowboy_stream_h.execute/3
        (cowboy 2.9.0) /home/dbern/ejecto/deps/cowboy/src/cowboy_stream_h.erl:295: :cowboy_stream_h.request_process/3
        (stdlib 4.0.1) proc_lib.erl:240: :proc_lib.init_p_do_apply/3
    

Code:

`lib/ejecto_web/live/home_live.ex`

    5       if connected?(socket), do: {:ok, socket}
    6       boom(:fail, :foo)
    7       {:ok, socket}
    8     end
    9   
    10>    defp boom(nil, :foo), do: :wat
    11     defp boom(1, :foo), do: :wat
    12     defp boom("a", :foo), do: :wat
    13     defp boom([], :foo), do: :wat
    14   
    15     def render(assigns) do
    
  Called with 2 arguments

  * `:fail`
  * `:foo`
  
  Attempted function clauses (showing 4 out of 4)

     defp boom(nil, :foo)
     defp boom(1, :foo)
     defp boom("a", :foo)
     defp boom([], :foo)
    

`lib/phoenix_live_view/utils.ex`
...

@dbernheisel
Copy link
Contributor Author

dbernheisel commented Jul 15, 2022

I just realized I used heroicons which are MIT licensed. Either we need to include their license or replace with license-free icons or remove the icons.

@josevalim
Copy link
Member

It looks great @dbernheisel, thank you!

Regarding the button, I think you can remove the border and background around the button, and keep it as a text as in "Show only app frames". The text can be in regular text color or the primary color (as you did), your call.

About the icon, we can go with UXWing, such as: https://uxwing.com/copy-icon/

@dbernheisel
Copy link
Contributor Author

Updated the icon and style. I also aligned the checkbox label and input.
Screenshot 2022-07-16 10 38 51 AM
Screenshot 2022-07-16 10 39 01 AM

@josevalim
Copy link
Member

Thank you! One last nitpick: can you please vertically align the text with "Show only app frames" and make it the same font color? That's what I meant by "regular text color" but apparently "Show only app frames" is not regular text color. Sorry about that.

@dbernheisel
Copy link
Contributor Author

adjusted the style. Good calls, np about the nitpicks as surely someone would come after me and fix these little visual annoyances :) and happy to get them now.

image

image

@josevalim josevalim merged commit 9b1ec93 into elixir-plug:main Jul 23, 2022
@josevalim
Copy link
Member

💚 💙 💜 💛 ❤️

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

2 participants