You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 

211 lines
8.1 KiB

;;; alchemist-server.el --- Interface to the Alchemist Elixir server. -*- lexical-binding: t -*-
;; Copyright © 2015 Samuel Tonini
;; Author: Samuel Tonini <tonini.samuel@gmail.com
;; This file is not part of GNU Emacs.
;; This program is free software: you can redistribute it and/or modify
;; it under the terms of the GNU General Public License as published by
;; the Free Software Foundation, either version 3 of the License, or
;; (at your option) any later version.
;; This program is distributed in the hope that it will be useful,
;; but WITHOUT ANY WARRANTY; without even the implied warranty of
;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
;; GNU General Public License for more details.
;; You should have received a copy of the GNU General Public License
;; along with this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Interface to the Alchemist Elixir server.
;;; Code:
(require 'alchemist-execute)
(defgroup alchemist-server nil
"Interface to the Alchemist Elixir server."
:prefix "alchemist-server-"
:group 'alchemist)
(defvar alchemist-server-processes '()
"Store running Alchemist server processes.")
(defvar alchemist-server-env "dev"
"Default environment in for the Alchemist server.")
(defvar alchemist-server-envs '("dev" "prod" "test" "shared")
"List of available Alchemist server environments.")
(defconst alchemist-server
(concat (file-name-directory load-file-name) "alchemist-server/run.exs")
"Path to the Alchemist server file.")
(defconst alchemist-server-command
(format "%s %s %s"
alchemist-execute-command
alchemist-server
alchemist-server-env)
"Alchemist server command.")
(defconst alchemist-server-codes '((server-eval "EVAL")
(server-defl "DEFL")
(server-info "INFO")
(server-docl "DOCL")
(server-comp "COMP"))
"Alchemist server API codes.")
(defun alchemist-server-start (env)
"Start alchemist server for the current mix project in specific ENV.
If a server already running, the current one will be killed and new one
will be started instead."
(interactive (list
(completing-read (format "(Alchemist-Server) run in environment: (default: %s) " alchemist-server-env)
alchemist-server-envs nil nil nil)))
(when (alchemist-server-process-p)
(kill-process (alchemist-server-process)))
(alchemist-server-start-in-env env))
(defun alchemist-server-start-if-not-running ()
"Start a new Alchemist server if not already running.
An Alchemist server will be started for the current Elixir mix project."
(unless (alchemist-server-process-p)
(alchemist-server-start-in-env alchemist-server-env)))
(defun alchemist-server-start-in-env (env)
"Start an Alchemist server with the ENV."
(let* ((process-name (alchemist-server-process-name))
(default-directory (if (string= process-name "alchemist-server")
default-directory
process-name))
(server-command (format "elixir %s %s" alchemist-server env))
(process (start-process-shell-command process-name "*alchemist-server*" server-command)))
(set-process-query-on-exit-flag process nil)
(alchemist-server--store-process process)))
(defun alchemist-server--store-process (process)
"Store PROCESS in `alchemist-server-processes'."
(let ((process-name (alchemist-server-process-name)))
(if (cdr (assoc process-name alchemist-server-processes))
(setq alchemist-server-processes
(delq (assoc process-name alchemist-server-processes) alchemist-server-processes)))
(add-to-list 'alchemist-server-processes (cons process-name process))))
(defun alchemist-server-process-p ()
"Return non-nil if a process for the current
Elixir mix project is live."
(process-live-p (alchemist-server-process)))
(defun alchemist-server-process ()
"Return process for the current Elixir mix project."
(cdr (assoc (alchemist-server-process-name) alchemist-server-processes)))
(defun alchemist-server-process-name ()
"Return process name for the current Elixir mix project."
(let* ((process-name (if (alchemist-project-elixir-p)
"alchemist-server"
(alchemist-project-root)))
(process-name (if process-name
process-name
"alchemist-server")))
process-name))
(defun alchemist-server-api-code (symbol)
"Return Alchemist server API code for SYMBOL."
(car (cdr (assoc symbol alchemist-server-codes))))
(defconst alchemist-server-code-end-marker-regex
(format "END-OF-\\(%s\\|%s\\|%s\\|%s\\|%s\\)$"
(alchemist-server-api-code 'server-eval)
(alchemist-server-api-code 'server-defl)
(alchemist-server-api-code 'server-info)
(alchemist-server-api-code 'server-docl)
(alchemist-server-api-code 'server-comp))
"Regular expression to identify Alchemist server API end markers.")
(defun alchemist-server-contains-end-marker-p (string)
"Return non-nil if STRING contain an Alchemist server API end marker."
(string-match-p alchemist-server-code-end-marker-regex string))
(defun alchemist-server-build-request-string (code &optional args)
"Build Alchemist server request string for CODE.
If ARGS available add them to the request string."
(let* ((code (car (cdr (assoc code alchemist-server-codes)))))
(if args
(format "%s %s\n" code args)
(format "%s\n" code))))
(defun alchemist-server-prepare-filter-output (output)
"Clean OUTPUT by remove Alchemist server API end markes."
(let* ((output (apply #'concat (reverse output)))
(output (replace-regexp-in-string alchemist-server-code-end-marker-regex "" output))
(output (replace-regexp-in-string "\n+$" "" output)))
output))
(defun alchemist-server-send-request (string filter)
"Send STRING to Alchemist server API and set FILTER to process."
(alchemist-server-start-if-not-running)
(set-process-filter (alchemist-server-process) filter)
(process-send-string (alchemist-server-process) string))
(defun alchemist-server-goto (args filter)
"Make an Alchemist server source request with ARGS.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-defl args) filter))
(defun alchemist-server-info (args filter)
"Make an Alchemist server mix request.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-info args) filter))
(defun alchemist-server-help-with-modules (filter)
"Make an Alchemist server modules request.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-info) filter))
(defun alchemist-server-help (args filter)
"Make an Alchemist server doc request with ARGS.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-docl args) filter))
(defun alchemist-server-eval (args filter)
"Make an Alchemist server evaluate request with FILE.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-eval args) filter))
(defun alchemist-server-complete-candidates (args filter)
"Make an Alchemist server complete request with ARGS.
Process server respond with FILTER."
(alchemist-server-start-if-not-running)
(alchemist-server-send-request (alchemist-server-build-request-string 'server-comp args) filter))
(defun alchemist-server-status ()
"Report the server status for the current Elixir project."
(interactive)
(message "Alchemist-Server-Status: [Project: %s Status: %s]"
(alchemist-server-process-name)
(if (alchemist-server-process-p)
"Connected"
"Not Connected")))
(provide 'alchemist-server)
;;; alchemist-server.el ends here