1;;; alchemist-server.el --- Interface to the Alchemist Elixir server. -*- lexical-binding: t -*- 2 3;; Copyright © 2015 Samuel Tonini 4 5;; Author: Samuel Tonini <tonini.samuel@gmail.com 6 7;; This file is not part of GNU Emacs. 8 9;; This program is free software: you can redistribute it and/or modify 10;; it under the terms of the GNU General Public License as published by 11;; the Free Software Foundation, either version 3 of the License, or 12;; (at your option) any later version. 13 14;; This program is distributed in the hope that it will be useful, 15;; but WITHOUT ANY WARRANTY; without even the implied warranty of 16;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 17;; GNU General Public License for more details. 18 19;; You should have received a copy of the GNU General Public License 20;; along with this program. If not, see <http://www.gnu.org/licenses/>. 21 22;;; Commentary: 23 24;; Interface to the Alchemist Elixir server. 25 26;;; Code: 27 28(require 'alchemist-execute) 29 30(defgroup alchemist-server nil 31 "Interface to the Alchemist Elixir server." 32 :prefix "alchemist-server-" 33 :group 'alchemist) 34 35(defvar alchemist-server-processes '() 36 "Store running Alchemist server processes.") 37 38(defvar alchemist-server-env "dev" 39 "Default environment in for the Alchemist server.") 40 41(defvar alchemist-server-envs '("dev" "prod" "test" "shared") 42 "List of available Alchemist server environments.") 43 44(defconst alchemist-server 45 (concat (file-name-directory load-file-name) "alchemist-server/run.exs") 46 "Path to the Alchemist server file.") 47 48(defconst alchemist-server-command 49 (format "%s %s %s" 50 alchemist-execute-command 51 alchemist-server 52 alchemist-server-env) 53 "Alchemist server command.") 54 55(defconst alchemist-server-codes '((server-eval "EVAL") 56 (server-defl "DEFL") 57 (server-info "INFO") 58 (server-docl "DOCL") 59 (server-comp "COMP")) 60 "Alchemist server API codes.") 61 62(defun alchemist-server-start (env) 63 "Start alchemist server for the current mix project in specific ENV. 64 65If a server already running, the current one will be killed and new one 66will be started instead." 67 (interactive (list 68 (completing-read (format "(Alchemist-Server) run in environment: (default: %s) " alchemist-server-env) 69 alchemist-server-envs nil nil nil))) 70 (when (alchemist-server-process-p) 71 (kill-process (alchemist-server-process))) 72 (alchemist-server-start-in-env env)) 73 74(defun alchemist-server-start-if-not-running () 75 "Start a new Alchemist server if not already running. 76 77An Alchemist server will be started for the current Elixir mix project." 78 (unless (alchemist-server-process-p) 79 (alchemist-server-start-in-env alchemist-server-env))) 80 81(defun alchemist-server-start-in-env (env) 82 "Start an Alchemist server with the ENV." 83 (let* ((process-name (alchemist-server-process-name)) 84 (default-directory (if (string= process-name "alchemist-server") 85 default-directory 86 process-name)) 87 (server-command (format "%s %s %s" 88 alchemist-execute-command 89 (shell-quote-argument alchemist-server) 90 (shell-quote-argument env))) 91 (process (start-process-shell-command process-name "*alchemist-server*" server-command))) 92 (set-process-query-on-exit-flag process nil) 93 (alchemist-server--store-process process))) 94 95(defun alchemist-server--store-process (process) 96 "Store PROCESS in `alchemist-server-processes'." 97 (let ((process-name (alchemist-server-process-name))) 98 (if (cdr (assoc process-name alchemist-server-processes)) 99 (setq alchemist-server-processes 100 (delq (assoc process-name alchemist-server-processes) alchemist-server-processes))) 101 (add-to-list 'alchemist-server-processes (cons process-name process)))) 102 103(defun alchemist-server-process-p () 104 "Return non-nil if a process for the current 105Elixir mix project is live." 106 (process-live-p (alchemist-server-process))) 107 108(defun alchemist-server-process () 109 "Return process for the current Elixir mix project." 110 (cdr (assoc (alchemist-server-process-name) alchemist-server-processes))) 111 112(defun alchemist-server-process-name () 113 "Return process name for the current Elixir mix project." 114 (let* ((process-name (if (alchemist-project-elixir-p) 115 "alchemist-server" 116 (alchemist-project-root))) 117 (process-name (if process-name 118 process-name 119 "alchemist-server"))) 120 process-name)) 121 122(defun alchemist-server-api-code (symbol) 123 "Return Alchemist server API code for SYMBOL." 124 (car (cdr (assoc symbol alchemist-server-codes)))) 125 126(defconst alchemist-server-code-end-marker-regex 127 (format "END-OF-\\(%s\\|%s\\|%s\\|%s\\|%s\\)$" 128 (alchemist-server-api-code 'server-eval) 129 (alchemist-server-api-code 'server-defl) 130 (alchemist-server-api-code 'server-info) 131 (alchemist-server-api-code 'server-docl) 132 (alchemist-server-api-code 'server-comp)) 133 "Regular expression to identify Alchemist server API end markers.") 134 135(defun alchemist-server-contains-end-marker-p (string) 136 "Return non-nil if STRING contain an Alchemist server API end marker." 137 (when string 138 (string-match-p alchemist-server-code-end-marker-regex string))) 139 140(defun alchemist-server-build-request-string (code &optional args) 141 "Build Alchemist server request string for CODE. 142 143If ARGS available add them to the request string." 144 (let* ((code (car (cdr (assoc code alchemist-server-codes))))) 145 (if args 146 (format "%s %s\n" code args) 147 (format "%s\n" code)))) 148 149(defun alchemist-server-prepare-filter-output (output) 150 "Clean OUTPUT by remove Alchemist server API end markes." 151 (let* ((output (apply #'concat (reverse output))) 152 (output (replace-regexp-in-string alchemist-server-code-end-marker-regex "" output)) 153 (output (replace-regexp-in-string "\n+$" "" output))) 154 output)) 155 156(defun alchemist-server-send-request (string filter) 157 "Send STRING to Alchemist server API and set FILTER to process." 158 (alchemist-server-start-if-not-running) 159 (set-process-filter (alchemist-server-process) filter) 160 (process-send-string (alchemist-server-process) string)) 161 162(defun alchemist-server-goto (args filter) 163 "Make an Alchemist server source request with ARGS. 164 165Process server respond with FILTER." 166 (alchemist-server-start-if-not-running) 167 (alchemist-server-send-request (alchemist-server-build-request-string 'server-defl args) filter)) 168 169(defun alchemist-server-info (args filter) 170 "Make an Alchemist server mix request. 171 172Process server respond with FILTER." 173 (alchemist-server-start-if-not-running) 174 (alchemist-server-send-request (alchemist-server-build-request-string 'server-info args) filter)) 175 176(defun alchemist-server-help-with-modules (filter) 177 "Make an Alchemist server modules request. 178 179Process server respond with FILTER." 180 (alchemist-server-start-if-not-running) 181 (alchemist-server-send-request (alchemist-server-build-request-string 'server-info) filter)) 182 183(defun alchemist-server-help (args filter) 184 "Make an Alchemist server doc request with ARGS. 185 186Process server respond with FILTER." 187 (alchemist-server-start-if-not-running) 188 (alchemist-server-send-request (alchemist-server-build-request-string 'server-docl args) filter)) 189 190(defun alchemist-server-eval (args filter) 191 "Make an Alchemist server evaluate request with FILE. 192 193Process server respond with FILTER." 194 (alchemist-server-start-if-not-running) 195 (alchemist-server-send-request (alchemist-server-build-request-string 'server-eval args) filter)) 196 197(defun alchemist-server-complete-candidates (args filter) 198 "Make an Alchemist server complete request with ARGS. 199 200Process server respond with FILTER." 201 (alchemist-server-start-if-not-running) 202 (alchemist-server-send-request (alchemist-server-build-request-string 'server-comp args) filter)) 203 204(defun alchemist-server-status () 205 "Report the server status for the current Elixir project." 206 (interactive) 207 (message "Alchemist-Server-Status: [Project: %s Status: %s]" 208 (alchemist-server-process-name) 209 (if (alchemist-server-process-p) 210 "Connected" 211 "Not Connected"))) 212 213(provide 'alchemist-server) 214 215;;; alchemist-server.el ends here 216