defmodule LivebookWeb.SessionLive.ShortcutsComponent do use LivebookWeb, :live_component @shortcuts %{ insert_mode: [ %{seq: ["esc"], desc: "Switch back to navigation mode"}, %{seq: ["ctrl", "↵"], desc: "Evaluate cell and stay in insert mode"}, %{seq: ["tab"], desc: "Autocomplete expression when applicable"}, %{ seq: ["ctrl", "␣"], transform_for_mac: false, desc: "Show completion list, use twice for details" } ], navigation_mode: [ %{seq: ["?"], desc: "Open this help modal"}, %{seq: ["j"], desc: "Focus next cell"}, %{seq: ["k"], desc: "Focus previous cell"}, %{seq: ["J"], desc: "Move cell down"}, %{seq: ["K"], desc: "Move cell up"}, %{seq: ["i"], desc: "Switch to insert mode"}, %{seq: ["n"], desc: "Insert Elixir cell below"}, %{seq: ["m"], desc: "Insert Markdown cell below"}, %{seq: ["N"], desc: "Insert Elixir cell above"}, %{seq: ["M"], desc: "Insert Markdown cell above"}, %{seq: ["S"], desc: "Add section"}, %{seq: ["dd"], desc: "Delete cell"}, %{seq: ["ee"], desc: "Evaluate cell"}, %{seq: ["es"], desc: "Evaluate section"}, %{seq: ["ea"], desc: "Evaluate all stale/new cells"}, %{seq: ["ej"], desc: "Evaluate cells below"}, %{seq: ["ex"], desc: "Cancel cell evaluation"}, %{seq: ["ss"], desc: "Toggle sections panel"}, %{seq: ["sr"], desc: "Show runtime settings"} ], universal: [ %{seq: ["ctrl", "s"], desc: "Save notebook"} ] } @impl true def mount(socket) do {:ok, assign(socket, shortcuts: @shortcuts)} end @impl true def render(assigns) do ~L"""

Keyboard shortcuts

Livebook highly embraces keyboard navigation to improve your productivity. It operates in one of two modes similarly to the Vim text editor. In navigation mode you move around the notebook and execute commands, whereas in the insert mode you have editor focus and directly modify the given cell content.

<%= render_shortcuts_section("Navigation mode", @shortcuts.navigation_mode, @platform) %> <%= render_shortcuts_section("Insert mode", @shortcuts.insert_mode, @platform) %> <%= render_shortcuts_section("Universal", @shortcuts.universal, @platform) %>
""" end defp render_shortcuts_section(title, shortcuts, platform) do {left, right} = split_in_half(shortcuts) assigns = %{title: title, left: left, right: right, platform: platform} ~L"""

<%= @title %>

<%= render_shortcuts_section_table(@left, @platform) %>
<%= render_shortcuts_section_table(@right, @platform) %>
""" end defp render_shortcuts_section_table(shortcuts, platform) do assigns = %{shortcuts: shortcuts, platform: platform} ~L""" <%= for shortcut <- @shortcuts do %> <% end %>
<%= render_shortcut_seq(shortcut, @platform) %> <%= shortcut.desc %>
""" end defp render_shortcut_seq(shortcut, platform) do seq = if platform == :mac and Map.get(shortcut, :transform_for_mac, true) do seq_for_mac(shortcut.seq) else shortcut.seq end joiner = remix_icon("add-line", class: "text-xl text-gray-600") elements = seq |> Enum.map(&render_key/1) |> Enum.intersperse(joiner) assigns = %{elements: elements} ~L"""
<%= for element <- @elements do %> <%= element %> <% end %>
""" end defp render_key(key) do content_tag("span", key, class: "bg-editor text-gray-200 text-sm font-semibold h-8 w-8 flex items-center justify-center rounded-lg inline-flex items-center" ) end defp seq_for_mac(seq) do Enum.map(seq, fn "ctrl" -> "⌘" "alt" -> "⌥" key -> key end) end defp split_in_half(list) do half_idx = list |> length() |> Kernel.+(1) |> div(2) Enum.split(list, half_idx) end end