645 lines
19 KiB
Markdown
645 lines
19 KiB
Markdown
# Elixir Integration Strategies
|
|
|
|
This document covers strategies for integrating Elixir with Emacs and org-babel, including execution modes, data conversion, and Mix project support.
|
|
|
|
## Table of Contents
|
|
|
|
- [Execution Modes](#execution-modes)
|
|
- [One-Shot Execution](#one-shot-execution)
|
|
- [IEx Session Management](#iex-session-management)
|
|
- [Mix Project Context](#mix-project-context)
|
|
- [Remote Shell (remsh)](#remote-shell-remsh)
|
|
- [Data Type Conversion](#data-type-conversion)
|
|
- [Error Handling](#error-handling)
|
|
- [elixir-mode Integration](#elixir-mode-integration)
|
|
- [Performance Considerations](#performance-considerations)
|
|
- [References](#references)
|
|
|
|
---
|
|
|
|
## Execution Modes
|
|
|
|
### Overview of Execution Strategies
|
|
|
|
| Mode | Command | Use Case | Startup Time | State |
|
|
|--------------|-------------------|----------------|--------------|-------------|
|
|
| One-shot | `elixir -e` | Simple scripts | ~500ms | None |
|
|
| Script file | `elixir file.exs` | Larger code | ~500ms | None |
|
|
| IEx session | `iex` | Interactive | Once | Persistent |
|
|
| Mix context | `iex -S mix` | Projects | Slower | Project |
|
|
| Remote shell | `iex --remsh` | Production | Fast | Remote node |
|
|
|
|
### Recommended Approach
|
|
|
|
For org-babel, we recommend:
|
|
|
|
1. **Default**: Script file execution (reliable, predictable)
|
|
2. **With `:session`**: IEx via comint (persistent state)
|
|
3. **With `:mix-project`**: Mix context execution
|
|
|
|
---
|
|
|
|
## One-Shot Execution
|
|
|
|
### Using `elixir -e`
|
|
|
|
For simple, single expressions:
|
|
|
|
```elisp
|
|
(defun ob-elixir--eval-simple (code)
|
|
"Evaluate CODE using elixir -e."
|
|
(shell-command-to-string
|
|
(format "elixir -e %s"
|
|
(shell-quote-argument code))))
|
|
```
|
|
|
|
**Pros**: Simple, no temp files
|
|
**Cons**: Limited code size, quoting issues
|
|
|
|
### Using Script Files (Recommended)
|
|
|
|
More robust for complex code:
|
|
|
|
```elisp
|
|
(defun ob-elixir--eval-script (code)
|
|
"Evaluate CODE using a temporary script file."
|
|
(let ((script-file (org-babel-temp-file "elixir-" ".exs")))
|
|
(with-temp-file script-file
|
|
(insert code))
|
|
(org-babel-eval
|
|
(format "elixir %s" (org-babel-process-file-name script-file))
|
|
"")))
|
|
```
|
|
|
|
### Capturing Return Values vs Output
|
|
|
|
```elisp
|
|
;; For :results output - capture stdout
|
|
(defun ob-elixir--eval-output (code)
|
|
"Execute CODE and capture stdout."
|
|
(ob-elixir--eval-script code))
|
|
|
|
;; For :results value - wrap to capture return value
|
|
(defconst ob-elixir--value-wrapper
|
|
"
|
|
_result_ = (
|
|
%s
|
|
)
|
|
IO.puts(inspect(_result_, limit: :infinity, printable_limit: :infinity, charlists: :as_lists))
|
|
"
|
|
"Wrapper to capture the return value of code.")
|
|
|
|
(defun ob-elixir--eval-value (code)
|
|
"Execute CODE and return its value."
|
|
(let ((wrapped (format ob-elixir--value-wrapper code)))
|
|
(string-trim (ob-elixir--eval-script wrapped))))
|
|
```
|
|
|
|
### Handling Multiline Output
|
|
|
|
```elisp
|
|
(defconst ob-elixir--value-wrapper-with-marker
|
|
"
|
|
_result_ = (
|
|
%s
|
|
)
|
|
IO.puts(\"__OB_ELIXIR_RESULT_START__\")
|
|
IO.puts(inspect(_result_, limit: :infinity, printable_limit: :infinity))
|
|
IO.puts(\"__OB_ELIXIR_RESULT_END__\")
|
|
"
|
|
"Wrapper with markers for reliable output parsing.")
|
|
|
|
(defun ob-elixir--extract-result (output)
|
|
"Extract result from OUTPUT between markers."
|
|
(when (string-match "__OB_ELIXIR_RESULT_START__\n\\(.*\\)\n__OB_ELIXIR_RESULT_END__"
|
|
output)
|
|
(match-string 1 output)))
|
|
```
|
|
|
|
---
|
|
|
|
## IEx Session Management
|
|
|
|
### Starting an IEx Session
|
|
|
|
```elisp
|
|
(defvar ob-elixir-iex-buffer-name "*ob-elixir-iex*"
|
|
"Buffer name for the IEx process.")
|
|
|
|
(defvar ob-elixir-prompt-regexp "^iex\\([0-9]+\\)> "
|
|
"Regexp matching the IEx prompt.")
|
|
|
|
(defvar ob-elixir-continued-prompt-regexp "^\\.\\.\\.\\([0-9]+\\)> "
|
|
"Regexp matching the IEx continuation prompt.")
|
|
|
|
(defun ob-elixir--start-iex-session (session-name &optional params)
|
|
"Start an IEx session named SESSION-NAME."
|
|
(let* ((buffer-name (format "*ob-elixir-%s*" session-name))
|
|
(buffer (get-buffer-create buffer-name))
|
|
(mix-project (when params (cdr (assq :mix-project params))))
|
|
(iex-args (when params (cdr (assq :iex-args params)))))
|
|
|
|
(unless (comint-check-proc buffer)
|
|
(with-current-buffer buffer
|
|
;; Set up environment
|
|
(setenv "TERM" "dumb")
|
|
(setenv "IEX_WITH_WERL" nil)
|
|
|
|
;; Start the process
|
|
(apply #'make-comint-in-buffer
|
|
(format "ob-elixir-%s" session-name)
|
|
buffer
|
|
"iex"
|
|
nil
|
|
(append
|
|
(when mix-project
|
|
(list "-S" "mix"))
|
|
(when iex-args
|
|
(split-string iex-args))))
|
|
|
|
;; Wait for initial prompt
|
|
(ob-elixir--wait-for-prompt buffer 10)
|
|
|
|
;; Configure IEx for programmatic use
|
|
(ob-elixir--configure-iex-session buffer)))
|
|
buffer))
|
|
```
|
|
|
|
### Configuring IEx for Non-Interactive Use
|
|
|
|
```elisp
|
|
(defun ob-elixir--configure-iex-session (buffer)
|
|
"Configure IEx in BUFFER for non-interactive use."
|
|
(with-current-buffer buffer
|
|
(let ((config-commands
|
|
'("IEx.configure(colors: [enabled: false])"
|
|
"IEx.configure(inspect: [limit: :infinity, printable_limit: :infinity])"
|
|
"Application.put_env(:elixir, :ansi_enabled, false)")))
|
|
(dolist (cmd config-commands)
|
|
(ob-elixir--send-to-iex buffer cmd)
|
|
(ob-elixir--wait-for-prompt buffer 5)))))
|
|
```
|
|
|
|
### Sending Code to IEx
|
|
|
|
```elisp
|
|
(defvar ob-elixir-eoe-marker "__OB_ELIXIR_EOE__"
|
|
"End-of-evaluation marker.")
|
|
|
|
(defun ob-elixir--send-to-iex (buffer code)
|
|
"Send CODE to IEx process in BUFFER."
|
|
(with-current-buffer buffer
|
|
(goto-char (point-max))
|
|
(insert code)
|
|
(comint-send-input nil t)))
|
|
|
|
(defun ob-elixir--eval-in-session (session code)
|
|
"Evaluate CODE in SESSION, return output."
|
|
(let* ((buffer (ob-elixir--get-or-create-session session))
|
|
(start-marker nil))
|
|
(with-current-buffer buffer
|
|
(goto-char (point-max))
|
|
(setq start-marker (point-marker))
|
|
|
|
;; Send the code
|
|
(ob-elixir--send-to-iex buffer code)
|
|
(ob-elixir--wait-for-prompt buffer 30)
|
|
|
|
;; Send EOE marker to clearly delineate output
|
|
(ob-elixir--send-to-iex buffer (format "\"%s\"" ob-elixir-eoe-marker))
|
|
(ob-elixir--wait-for-prompt buffer 5)
|
|
|
|
;; Extract output
|
|
(ob-elixir--extract-session-output buffer start-marker))))
|
|
|
|
(defun ob-elixir--wait-for-prompt (buffer timeout)
|
|
"Wait for IEx prompt in BUFFER with TIMEOUT seconds."
|
|
(with-current-buffer buffer
|
|
(let ((end-time (+ (float-time) timeout)))
|
|
(while (and (< (float-time) end-time)
|
|
(not (ob-elixir--at-prompt-p)))
|
|
(accept-process-output (get-buffer-process buffer) 0.1)
|
|
(goto-char (point-max))))))
|
|
|
|
(defun ob-elixir--at-prompt-p ()
|
|
"Return t if point is at an IEx prompt."
|
|
(save-excursion
|
|
(beginning-of-line)
|
|
(looking-at ob-elixir-prompt-regexp)))
|
|
```
|
|
|
|
### Cleaning Session Output
|
|
|
|
```elisp
|
|
(defun ob-elixir--clean-iex-output (output)
|
|
"Clean OUTPUT from IEx session."
|
|
(let ((cleaned output))
|
|
;; Remove ANSI escape codes
|
|
(setq cleaned (ansi-color-filter-apply cleaned))
|
|
;; Remove prompts
|
|
(setq cleaned (replace-regexp-in-string
|
|
"^iex([0-9]+)> " "" cleaned))
|
|
(setq cleaned (replace-regexp-in-string
|
|
"^\\.\\.\\.([0-9]+)> " "" cleaned))
|
|
;; Remove EOE marker
|
|
(setq cleaned (replace-regexp-in-string
|
|
(regexp-quote (format "\"%s\"" ob-elixir-eoe-marker))
|
|
"" cleaned))
|
|
;; Remove trailing whitespace
|
|
(string-trim cleaned)))
|
|
```
|
|
|
|
---
|
|
|
|
## Mix Project Context
|
|
|
|
### Detecting Mix Projects
|
|
|
|
```elisp
|
|
(defun ob-elixir--find-mix-project (dir)
|
|
"Find mix.exs file starting from DIR, searching up."
|
|
(let ((mix-file (locate-dominating-file dir "mix.exs")))
|
|
(when mix-file
|
|
(file-name-directory mix-file))))
|
|
|
|
(defun ob-elixir--in-mix-project-p ()
|
|
"Return t if current buffer is in a Mix project."
|
|
(ob-elixir--find-mix-project default-directory))
|
|
```
|
|
|
|
### Executing in Mix Context
|
|
|
|
```elisp
|
|
(defun ob-elixir--eval-with-mix (code project-dir &optional mix-env)
|
|
"Evaluate CODE in the context of Mix project at PROJECT-DIR."
|
|
(let* ((default-directory project-dir)
|
|
(script-file (org-babel-temp-file "elixir-" ".exs"))
|
|
(env-vars (when mix-env
|
|
(format "MIX_ENV=%s " mix-env))))
|
|
(with-temp-file script-file
|
|
(insert code))
|
|
(shell-command-to-string
|
|
(format "%smix run %s"
|
|
(or env-vars "")
|
|
(org-babel-process-file-name script-file)))))
|
|
```
|
|
|
|
### Compiling Before Execution
|
|
|
|
```elisp
|
|
(defun ob-elixir--ensure-compiled (project-dir)
|
|
"Ensure Mix project at PROJECT-DIR is compiled."
|
|
(let ((default-directory project-dir))
|
|
(shell-command-to-string "mix compile --force-check")))
|
|
|
|
(defun ob-elixir--eval-with-compilation (code project-dir)
|
|
"Compile and evaluate CODE in PROJECT-DIR."
|
|
(ob-elixir--ensure-compiled project-dir)
|
|
(ob-elixir--eval-with-mix code project-dir))
|
|
```
|
|
|
|
### Using Mix Aliases
|
|
|
|
```elisp
|
|
(defun ob-elixir--run-mix-task (task project-dir &optional args)
|
|
"Run Mix TASK in PROJECT-DIR with ARGS."
|
|
(let ((default-directory project-dir))
|
|
(shell-command-to-string
|
|
(format "mix %s %s" task (or args "")))))
|
|
```
|
|
|
|
---
|
|
|
|
## Remote Shell (remsh)
|
|
|
|
### Connecting to Running Nodes
|
|
|
|
```elisp
|
|
(defun ob-elixir--start-remsh-session (node-name &optional cookie sname)
|
|
"Connect to remote Elixir node NODE-NAME.
|
|
|
|
COOKIE is the Erlang cookie (optional).
|
|
SNAME is the short name for the local node."
|
|
(let* ((local-name (or sname (format "ob_elixir_%d" (random 10000))))
|
|
(buffer-name (format "*ob-elixir-remsh-%s*" node-name))
|
|
(buffer (get-buffer-create buffer-name))
|
|
(args (append
|
|
(list "--sname" local-name)
|
|
(when cookie (list "--cookie" cookie))
|
|
(list "--remsh" node-name))))
|
|
|
|
(unless (comint-check-proc buffer)
|
|
(with-current-buffer buffer
|
|
(apply #'make-comint-in-buffer
|
|
(format "ob-elixir-remsh-%s" node-name)
|
|
buffer
|
|
"iex"
|
|
nil
|
|
args)
|
|
(ob-elixir--wait-for-prompt buffer 30)
|
|
(ob-elixir--configure-iex-session buffer)))
|
|
buffer))
|
|
```
|
|
|
|
### Remote Evaluation
|
|
|
|
```elisp
|
|
(defun ob-elixir--eval-remote (code node-name &optional cookie)
|
|
"Evaluate CODE on remote NODE-NAME."
|
|
(let ((session (ob-elixir--start-remsh-session node-name cookie)))
|
|
(ob-elixir--eval-in-session session code)))
|
|
```
|
|
|
|
---
|
|
|
|
## Data Type Conversion
|
|
|
|
### Elisp to Elixir
|
|
|
|
```elisp
|
|
(defun ob-elixir--elisp-to-elixir (value)
|
|
"Convert Elisp VALUE to Elixir literal syntax."
|
|
(pcase value
|
|
('nil "nil")
|
|
('t "true")
|
|
((pred numberp) (number-to-string value))
|
|
((pred stringp) (ob-elixir--format-string value))
|
|
((pred symbolp) (ob-elixir--format-atom value))
|
|
((pred vectorp) (ob-elixir--format-tuple value))
|
|
((pred listp)
|
|
(cond
|
|
((ob-elixir--alist-p value) (ob-elixir--format-keyword-list value))
|
|
((ob-elixir--plist-p value) (ob-elixir--format-map value))
|
|
(t (ob-elixir--format-list value))))
|
|
(_ (format "%S" value))))
|
|
|
|
(defun ob-elixir--format-string (str)
|
|
"Format STR as Elixir string."
|
|
(format "\"%s\""
|
|
(replace-regexp-in-string
|
|
"\\\\" "\\\\\\\\"
|
|
(replace-regexp-in-string
|
|
"\"" "\\\\\""
|
|
(replace-regexp-in-string
|
|
"\n" "\\\\n"
|
|
str)))))
|
|
|
|
(defun ob-elixir--format-atom (sym)
|
|
"Format symbol SYM as Elixir atom."
|
|
(let ((name (symbol-name sym)))
|
|
(if (string-match-p "^[a-z_][a-zA-Z0-9_]*[?!]?$" name)
|
|
(format ":%s" name)
|
|
(format ":\"%s\"" name))))
|
|
|
|
(defun ob-elixir--format-list (lst)
|
|
"Format LST as Elixir list."
|
|
(format "[%s]"
|
|
(mapconcat #'ob-elixir--elisp-to-elixir lst ", ")))
|
|
|
|
(defun ob-elixir--format-tuple (vec)
|
|
"Format vector VEC as Elixir tuple."
|
|
(format "{%s}"
|
|
(mapconcat #'ob-elixir--elisp-to-elixir (append vec nil) ", ")))
|
|
|
|
(defun ob-elixir--format-keyword-list (alist)
|
|
"Format ALIST as Elixir keyword list."
|
|
(format "[%s]"
|
|
(mapconcat
|
|
(lambda (pair)
|
|
(format "%s: %s"
|
|
(car pair)
|
|
(ob-elixir--elisp-to-elixir (cdr pair))))
|
|
alist ", ")))
|
|
|
|
(defun ob-elixir--format-map (plist)
|
|
"Format PLIST as Elixir map."
|
|
(let ((pairs '()))
|
|
(while plist
|
|
(push (format "%s => %s"
|
|
(ob-elixir--elisp-to-elixir (car plist))
|
|
(ob-elixir--elisp-to-elixir (cadr plist)))
|
|
pairs)
|
|
(setq plist (cddr plist)))
|
|
(format "%%{%s}" (mapconcat #'identity (nreverse pairs) ", "))))
|
|
```
|
|
|
|
### Elixir to Elisp
|
|
|
|
```elisp
|
|
(defun ob-elixir--parse-result (output)
|
|
"Parse Elixir OUTPUT into Elisp value."
|
|
(let ((trimmed (string-trim output)))
|
|
(cond
|
|
;; nil
|
|
((string= trimmed "nil") nil)
|
|
|
|
;; Booleans
|
|
((string= trimmed "true") t)
|
|
((string= trimmed "false") nil)
|
|
|
|
;; Numbers
|
|
((string-match-p "^-?[0-9]+$" trimmed)
|
|
(string-to-number trimmed))
|
|
((string-match-p "^-?[0-9]+\\.[0-9]+\\(e[+-]?[0-9]+\\)?$" trimmed)
|
|
(string-to-number trimmed))
|
|
|
|
;; Atoms (convert to symbols)
|
|
((string-match "^:\\([a-zA-Z_][a-zA-Z0-9_]*\\)$" trimmed)
|
|
(intern (match-string 1 trimmed)))
|
|
|
|
;; Strings
|
|
((string-match "^\"\\(.*\\)\"$" trimmed)
|
|
(match-string 1 trimmed))
|
|
|
|
;; Lists/tuples - use org-babel-script-escape
|
|
((string-match-p "^\\[.*\\]$" trimmed)
|
|
(org-babel-script-escape trimmed))
|
|
((string-match-p "^{.*}$" trimmed)
|
|
(org-babel-script-escape
|
|
(replace-regexp-in-string "^{\\(.*\\)}$" "[\\1]" trimmed)))
|
|
|
|
;; Maps - convert to alist
|
|
((string-match-p "^%{.*}$" trimmed)
|
|
(ob-elixir--parse-map trimmed))
|
|
|
|
;; Default: return as string
|
|
(t trimmed))))
|
|
|
|
(defun ob-elixir--parse-map (map-string)
|
|
"Parse Elixir MAP-STRING to alist."
|
|
;; Simplified - for complex maps, use JSON encoding
|
|
(let ((content (substring map-string 2 -1))) ; Remove %{ and }
|
|
(mapcar
|
|
(lambda (pair)
|
|
(when (string-match "\\(.+?\\) => \\(.+\\)" pair)
|
|
(cons (ob-elixir--parse-result (match-string 1 pair))
|
|
(ob-elixir--parse-result (match-string 2 pair)))))
|
|
(split-string content ", "))))
|
|
```
|
|
|
|
### Using JSON for Complex Data
|
|
|
|
For complex nested structures, JSON is more reliable:
|
|
|
|
```elisp
|
|
(defconst ob-elixir--json-wrapper
|
|
"
|
|
_result_ = (
|
|
%s
|
|
)
|
|
IO.puts(Jason.encode!(_result_))
|
|
"
|
|
"Wrapper that outputs result as JSON.")
|
|
|
|
(defun ob-elixir--eval-as-json (code)
|
|
"Evaluate CODE and parse result as JSON."
|
|
(let* ((wrapped (format ob-elixir--json-wrapper code))
|
|
(output (ob-elixir--eval-script wrapped)))
|
|
(json-read-from-string (string-trim output))))
|
|
```
|
|
|
|
---
|
|
|
|
## Error Handling
|
|
|
|
### Detecting Elixir Errors
|
|
|
|
```elisp
|
|
(defconst ob-elixir-error-patterns
|
|
'("^\\*\\* (\\(\\w+Error\\))" ; ** (RuntimeError) ...
|
|
"^\\*\\* (\\(\\w+\\)) \\(.+\\)" ; ** (exit) ...
|
|
"^\\(CompileError\\)" ; CompileError ...
|
|
"^\\(SyntaxError\\)") ; SyntaxError ...
|
|
"Patterns matching Elixir error output.")
|
|
|
|
(defun ob-elixir--error-p (output)
|
|
"Return error info if OUTPUT contains an error, nil otherwise."
|
|
(catch 'found
|
|
(dolist (pattern ob-elixir-error-patterns)
|
|
(when (string-match pattern output)
|
|
(throw 'found (list :type (match-string 1 output)
|
|
:message output))))))
|
|
```
|
|
|
|
### Signaling Errors to Org
|
|
|
|
```elisp
|
|
(define-error 'ob-elixir-error "Elixir evaluation error")
|
|
(define-error 'ob-elixir-compile-error "Elixir compilation error" 'ob-elixir-error)
|
|
(define-error 'ob-elixir-runtime-error "Elixir runtime error" 'ob-elixir-error)
|
|
|
|
(defun ob-elixir--handle-error (output)
|
|
"Handle error in OUTPUT, signaling appropriate condition."
|
|
(when-let ((error-info (ob-elixir--error-p output)))
|
|
(let ((type (plist-get error-info :type))
|
|
(message (plist-get error-info :message)))
|
|
(cond
|
|
((member type '("CompileError" "SyntaxError" "TokenMissingError"))
|
|
(signal 'ob-elixir-compile-error (list message)))
|
|
(t
|
|
(signal 'ob-elixir-runtime-error (list message)))))))
|
|
```
|
|
|
|
### Timeout Handling
|
|
|
|
```elisp
|
|
(defcustom ob-elixir-timeout 30
|
|
"Default timeout in seconds for Elixir evaluation."
|
|
:type 'integer
|
|
:group 'ob-elixir)
|
|
|
|
(defun ob-elixir--eval-with-timeout (code timeout)
|
|
"Evaluate CODE with TIMEOUT seconds limit."
|
|
(with-timeout (timeout
|
|
(error "Elixir evaluation timed out after %d seconds" timeout))
|
|
(ob-elixir--eval-script code)))
|
|
```
|
|
|
|
---
|
|
|
|
## elixir-mode Integration
|
|
|
|
### Syntax Highlighting
|
|
|
|
```elisp
|
|
;; Register with org-src for editing
|
|
(add-to-list 'org-src-lang-modes '("elixir" . elixir))
|
|
|
|
;; If elixir-mode isn't available, use a fallback
|
|
(unless (fboundp 'elixir-mode)
|
|
(add-to-list 'org-src-lang-modes '("elixir" . prog)))
|
|
```
|
|
|
|
### Using elixir-mode Functions
|
|
|
|
```elisp
|
|
(defun ob-elixir--format-code (code)
|
|
"Format CODE using mix format if available."
|
|
(when (and (executable-find "mix")
|
|
(> (length code) 0))
|
|
(with-temp-buffer
|
|
(insert code)
|
|
(let ((temp-file (make-temp-file "elixir-format" nil ".ex")))
|
|
(unwind-protect
|
|
(progn
|
|
(write-region (point-min) (point-max) temp-file)
|
|
(when (= 0 (call-process "mix" nil nil nil
|
|
"format" temp-file))
|
|
(erase-buffer)
|
|
(insert-file-contents temp-file)
|
|
(buffer-string)))
|
|
(delete-file temp-file))))))
|
|
```
|
|
|
|
---
|
|
|
|
## Performance Considerations
|
|
|
|
### Caching Compilation
|
|
|
|
```elisp
|
|
(defvar ob-elixir--module-cache (make-hash-table :test 'equal)
|
|
"Cache of compiled Elixir modules.")
|
|
|
|
(defun ob-elixir--get-cached-module (code)
|
|
"Get cached module for CODE, or compile and cache."
|
|
(let ((hash (md5 code)))
|
|
(or (gethash hash ob-elixir--module-cache)
|
|
(let ((module-name (format "ObElixir_%s" (substring hash 0 8))))
|
|
(ob-elixir--compile-module module-name code)
|
|
(puthash hash module-name ob-elixir--module-cache)
|
|
module-name))))
|
|
```
|
|
|
|
### Reusing Sessions
|
|
|
|
```elisp
|
|
(defcustom ob-elixir-reuse-sessions t
|
|
"Whether to reuse sessions between evaluations.
|
|
When non-nil, sessions persist until explicitly killed."
|
|
:type 'boolean
|
|
:group 'ob-elixir)
|
|
```
|
|
|
|
### Startup Time Optimization
|
|
|
|
```elisp
|
|
;; Pre-start a session on package load (optional)
|
|
(defun ob-elixir-warm-up ()
|
|
"Pre-start an IEx session for faster first evaluation."
|
|
(interactive)
|
|
(ob-elixir--start-iex-session "warmup"))
|
|
```
|
|
|
|
---
|
|
|
|
## References
|
|
|
|
- [Elixir Documentation](https://elixir-lang.org/docs.html)
|
|
- [IEx Documentation](https://hexdocs.pm/iex/IEx.html)
|
|
- [Mix Documentation](https://hexdocs.pm/mix/Mix.html)
|
|
- [Erlang Distribution Protocol](https://www.erlang.org/doc/reference_manual/distributed.html)
|
|
- [elixir-mode GitHub](https://github.com/elixir-editors/emacs-elixir)
|
|
- [inf-elixir for REPL](https://github.com/J3RN/inf-elixir)
|
|
- [Elixir LS (Language Server)](https://github.com/elixir-lsp/elixir-ls)
|