Current section

Files

Jump to
corex lib components code.ex
Raw

lib/components/code.ex

defmodule Corex.Code do
@moduledoc ~S'''
Displays syntax-highlighted code with Makeup.
## Installation
Add `makeup` and the lexer packages for each language you use:
```elixir
defp deps do
[
{:makeup, "~> 1.2"},
{:makeup_elixir, "~> 1.0.1 or ~> 1.1"},
{:makeup_html, "~> 1.0"},
{:makeup_css, "~> 1.0"},
{:makeup_js, "~> 1.0"}
]
end
```
Only `makeup` is required. Add the lexer packages you need; without a lexer, code renders as plain escaped text.
## Examples
### Basic Usage
```heex
<.code code="def hello, do: :world" />
```
### Multi-line with heredoc
Use `"""` heredoc for readable multi-line code in templates:
```heex
<.code code={"""
defmodule Hello do
def world, do: "Hello, World!"
end
"""} />
```
### Loading from a file
```heex
<.code code={File.read!("priv/code_examples/example.ex")} language={:elixir} />
```
Or in a controller, load the file and pass the contents:
```elixir
def my_page(conn, _params) do
code = File.read!("priv/code_examples/example.ex")
render(conn, :my_page, code: code)
end
```
```heex
<.code code={@code} language={:elixir} />
```
The `language` attribute maps to Makeup's registry (e.g. `:elixir``"elixir"`).
Add the corresponding lexer package to your deps for each language. Without it, code renders as plain escaped text.
## Styling
Use data attributes to target elements:
```css
[data-scope="code"][data-part="root"] {}
[data-scope="code"][data-part="content"] {}
```
With Corex Design, syntax highlighting styles are included in
`code.css`. Nothing else is required.
Without Corex Design, run `mix corex.code` to generate the Makeup stylesheet and
import it in your CSS:
```bash
mix corex.code
mix corex.code assets/styles/syntax.css
mix corex.code --force
```
```css
@import "./code_highlight.css";
```
'''
@doc type: :component
use Phoenix.Component
attr(:code, :string, required: true, doc: "The raw source code to display")
attr(:language, :atom,
default: :elixir,
doc:
"The language name in Makeup's registry (e.g. :elixir, :html). Add the lexer package to deps; otherwise renders as plain escaped text."
)
attr(:inline, :boolean, default: false, doc: "Whether to display the code inline")
attr(:rest, :global)
def code(assigns) do
makeup = Module.concat(["Elixir", "Makeup"])
unless Code.ensure_loaded?(makeup) do
raise """
Corex.Code requires makeup.
Add it to your mix.exs:
{:makeup, "~> 1.2"}
Add lexer packages for each language you use (e.g. makeup_elixir, makeup_html).
"""
end
assigns =
assigns
|> assign(:lexer, lexer_for(assigns.language))
|> then(&assign(&1, :highlighted_html, highlight_code(&1)))
~H"""
<pre
:if={!@inline}
data-scope="code"
data-part="root"
tabindex="0"
{@rest}
>
<code data-scope="code" data-part="content">{Phoenix.HTML.raw(@highlighted_html)}</code>
</pre>
<code :if={@inline} data-scope="code" data-part="root" {@rest}>
<span data-scope="code" data-part="content">{Phoenix.HTML.raw(@highlighted_html)}</span>
</code>
"""
end
defp lexer_for(language) do
name = to_string(language)
registry = Module.concat(["Elixir", "Makeup", "Registry"])
case registry.fetch_lexer_by_name(name) do
{:ok, {lexer, _opts}} -> lexer
:error -> nil
end
end
defp highlight_code(assigns) do
case assigns.lexer do
nil ->
assigns.code
|> Phoenix.HTML.html_escape()
|> Phoenix.HTML.safe_to_string()
lexer ->
makeup = Module.concat(["Elixir", "Makeup"])
makeup.highlight_inner_html(assigns.code, lexer: lexer)
end
end
end