Skip to content
This repository was archived by the owner on Jul 16, 2026. It is now read-only.
This repository was archived by the owner on Jul 16, 2026. It is now read-only.

Highlight code using makeup_syntect #61

Description

@SteffenDE

Hello everyone,

I created https://github.com/elixir-makeup/makeup_syntect a while ago to add syntax highlighting for many languages to ExDoc. I am wondering if it may also be worth to be included on preview.hex.pm. The only concern I have is that I don't know if it is suitable for a production environment, as it's primarily meant to run at compile time when generating docs and I haven't used it in a project at runtime yet. It is based on syntect, a Rust library that uses Sublime syntax files and mapping its tokens to makeup tokens.

I did some local tests using Benchee and it seems to run quite well performance wise:

defmodule MyBench do
  def run do
    Benchee.run(
      %{
        "elixir" => fn -> Makeup.highlight(elixir_code(), language: "elixir") end,
        "javascript" => fn -> Makeup.highlight(javascript_code(), language: "javascript") end
      },
      time: 10,
      memory_time: 2
    )
  end

  defp elixir_code do
    ~S"""
    defmodule Phoenix.LiveView.AsyncResult do
      @moduledoc ~S'''
      Provides a data structure for tracking the state of an async assign.

      See the `Async Operations` section of the `Phoenix.LiveView` docs for more information.

      ## Fields

        * `:ok?` - When true, indicates the `:result` has been set successfully at least once.
        * `:loading` - The current loading state
        * `:failed` - The current failed state
        * `:result` - The successful result of the async task
      '''

      defstruct ok?: false,
                loading: nil,
                failed: nil,
                result: nil

      alias Phoenix.LiveView.AsyncResult

      @doc \"\"\"
      Creates an async result in loading state.

      ## Examples

          iex> result = AsyncResult.loading()
          iex> result.loading
          true
          iex> result.ok?
          false

      \"\"\"
      def loading do
        %AsyncResult{loading: true}
      end

      @doc \"\"\"
      Updates the loading state.

      When loading, the failed state will be reset to `nil`.

      ## Examples

          iex> result = AsyncResult.loading(%{my: :loading_state})
          iex> result.loading
          %{my: :loading_state}
          iex> result = AsyncResult.loading(result)
          iex> result.loading
          true

      \"\"\"
      def loading(%AsyncResult{} = result) do
        %{result | loading: true, failed: nil}
      end

      def loading(loading_state) do
        %AsyncResult{loading: loading_state, failed: nil}
      end
    end
    """
  end

  defp javascript_code do
    """
    const ARIA = {
      anyOf(instance, classes) {
        return classes.find((name) => instance instanceof name);
      },

      isFocusable(el, interactiveOnly) {
        return (
          (el instanceof HTMLAnchorElement && el.rel !== "ignore") ||
          (el instanceof HTMLAreaElement && el.href !== undefined) ||
          (!el.disabled &&
            this.anyOf(el, [
              HTMLInputElement,
              HTMLSelectElement,
              HTMLTextAreaElement,
              HTMLButtonElement,
            ])) ||
          el instanceof HTMLIFrameElement ||
          (el.tabIndex >= 0 && el.getAttribute("aria-hidden") !== "true") ||
          (!interactiveOnly &&
            el.getAttribute("tabindex") !== null &&
            el.getAttribute("aria-hidden") !== "true")
        );
      },

      attemptFocus(el, interactiveOnly) {
        if (this.isFocusable(el, interactiveOnly)) {
          try {
            el.focus();
          } catch {
            // that's fine
          }
        }
        return !!document.activeElement && document.activeElement.isSameNode(el);
      },

      focusFirstInteractive(el) {
        let child = el.firstElementChild;
        while (child) {
          if (this.attemptFocus(child, true) || this.focusFirstInteractive(child)) {
            return true;
          }
          child = child.nextElementSibling;
        }
      },

      focusFirst(el) {
        let child = el.firstElementChild;
        while (child) {
          if (this.attemptFocus(child) || this.focusFirst(child)) {
            return true;
          }
          child = child.nextElementSibling;
        }
      },

      focusLast(el) {
        let child = el.lastElementChild;
        while (child) {
          if (this.attemptFocus(child) || this.focusLast(child)) {
            return true;
          }
          child = child.previousElementSibling;
        }
      },
    };
    export default ARIA;
    """
  end
end
Operating System: macOS
CPU Information: Apple M1 Max
Number of Available Cores: 10
Available memory: 32 GB
Elixir 1.18.4
Erlang 28.0.1
JIT enabled: true

Benchmark suite executing with the following configuration:
warmup: 2 s
time: 10 s
memory time: 2 s
reduction time: 0 ns
parallel: 1
inputs: none specified
Estimated total run time: 28 s

Benchmarking elixir ...
Benchmarking javascript ...
Calculating statistics...
Formatting results...

Name                 ips        average  deviation         median         99th %
javascript        906.67        1.10 ms    ±20.52%        1.06 ms        1.64 ms
elixir            479.97        2.08 ms     ±9.80%        2.04 ms        2.63 ms

Comparison: 
javascript        906.67
elixir            479.97 - 1.89x slower +0.98 ms

Memory usage statistics:

Name               average  deviation         median         99th %
javascript         4.05 MB     ±0.02%        4.05 MB        4.05 MB
elixir            10.05 MB     ±0.00%       10.05 MB       10.05 MB

Comparison: 
javascript         4.05 MB
elixir            10.05 MB - 2.48x memory usage +6.00 MB

If you're interested, I am happy to send a pull request!

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Fields

    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions