Skip to content

Highlight code using makeup_syntect #61

@SteffenDE

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

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions