;;; geiser-connection.el -- talking to a scheme process ;; Copyright (C) 2009, 2010 Jose Antonio Ortega Ruiz ;; This program is free software; you can redistribute it and/or ;; modify it under the terms of the Modified BSD License. You should ;; have received a copy of the license along with this program. If ;; not, see . ;; Start date: Sat Feb 07, 2009 21:11 ;; Connection datatype and functions for managing request queues ;; between emacs and inferior guile processes. (require 'geiser-log) (require 'geiser-syntax) (require 'geiser-base) (require 'geiser-impl) (require 'comint) (require 'advice) ;;; Buffer connections: (make-variable-buffer-local (defvar geiser-con--connection nil)) (defun geiser-con--get-connection (buffer/proc) (if (processp buffer/proc) (geiser-con--get-connection (process-buffer buffer/proc)) (with-current-buffer buffer/proc geiser-con--connection))) ;;; Request datatype: (defun geiser-con--make-request (con str cont &optional sender-buffer) (list :geiser-connection-request (cons :id (geiser-con--connection-inc-count con)) (cons :string str) (cons :continuation cont) (cons :buffer (or sender-buffer (current-buffer))) (cons :connection con))) (defsubst geiser-con--request-p (req) (and (listp req) (eq (car req) :geiser-connection-request))) (defsubst geiser-con--request-id (req) (cdr (assoc :id req))) (defsubst geiser-con--request-string (req) (cdr (assoc :string req))) (defsubst geiser-con--request-continuation (req) (cdr (assoc :continuation req))) (defsubst geiser-con--request-buffer (req) (cdr (assoc :buffer req))) (defsubst geiser-con--request-connection (req) (cdr (assoc :connection req))) (defsubst geiser-con--request-deactivate (req) (setcdr (assoc :continuation req) nil)) (defsubst geiser-con--request-deactivated-p (req) (null (cdr (assoc :continuation req)))) ;;; Connection datatype: (defsubst geiser-con--make-connection (buffer) (list :geiser-connection (cons :requests (list)) (cons :current nil) (cons :count 0) (cons :completed (make-hash-table :weakness 'value)) (cons :buffer buffer) (cons :reply (geiser-con--make-reply-buffer buffer)))) (defvar geiser-con--eot-regexp nil) (geiser-impl--register-local-variable 'geiser-con--eot-regexp 'eot-regexp nil "A regular expression used to detect end of transmissions. By default, Geiser uses the prompt regexp.") (defun geiser-con--make-reply-buffer (buffer) (let ((name (concat " geiser-con-reply: " (buffer-name buffer))) (eot (with-current-buffer buffer geiser-con--eot-regexp))) (with-current-buffer (get-buffer-create name) (setq geiser-con--eot-regexp eot) (current-buffer)))) (defsubst geiser-con--connection-p (c) (and (listp c) (eq (car c) :geiser-connection))) (defsubst geiser-con--connection-buffer (c) (cdr (assoc :buffer c))) (defsubst geiser-con--connection-process (c) (get-buffer-process (geiser-con--connection-buffer c))) (defsubst geiser-con--connection-requests (c) (cdr (assoc :requests c))) (defsubst geiser-con--connection-current-request (c) (cdr (assoc :current c))) (defsubst geiser-con--connection-reply-buffer (c) (cdr (assoc :reply c))) (defsubst geiser-con--connection-completed (c r) (geiser-con--request-deactivate req) (puthash (geiser-con--request-id r) r (cdr (assoc :completed c)))) (defun geiser-con--connection-clean-current-request (c) (let* ((cell (assoc :current c)) (req (cdr cell))) (when req (geiser-con--connection-completed c req) (setcdr cell nil)))) (defun geiser-con--connection-add-request (c r) (let ((reqs (assoc :requests c))) (setcdr reqs (append (cdr reqs) (list r))))) (defsubst geiser-con--connection-completed-p (c id) (gethash id (cdr (assoc :completed c)))) (defun geiser-con--connection-pop-request (c) (let* ((reqs (assoc :requests c)) (current (assoc :current c)) (old-current (cdr current)) (new-current (cadr reqs)) (new-reqs (cddr reqs))) (when old-current (geiser-con--connection-completed c old-current)) (setcdr reqs new-reqs) (if (and new-current (geiser-con--request-deactivated-p new-current)) (geiser-con--connection-pop-request c) (setcdr current new-current)))) (defun geiser-con--connection-inc-count (c) (let* ((cnt (assoc :count c)) (new (1+ (cdr cnt)))) (setcdr cnt new) new)) ;;; Connection setup: (make-variable-buffer-local (defvar geiser-con--debugging-prompt-regexp nil)) (make-variable-buffer-local (defvar geiser-con--debugging-inhibits-eval t)) (make-variable-buffer-local (defvar geiser-con--debugging-preamble-regexp nil)) (defun geiser-con--is-debugging () (and geiser-con--debugging-prompt-regexp geiser-con--debugging-inhibits-eval comint-last-prompt-overlay (string-match-p geiser-con--debugging-prompt-regexp (buffer-substring (overlay-start comint-last-prompt-overlay) (overlay-end comint-last-prompt-overlay))))) (defsubst geiser-con--has-entered-debugger (con) (with-current-buffer (geiser-con--connection-buffer con) (and geiser-con--debugging-prompt-regexp (re-search-backward geiser-con--debugging-prompt-regexp nil t) (or (null geiser-con--debugging-preamble-regexp) (save-excursion (re-search-backward geiser-con--debugging-preamble-regexp nil t)))))) (defun geiser-con--connection-teardown () (when geiser-con--connection (kill-buffer (geiser-con--connection-reply-buffer geiser-con--connection)))) (defun geiser-con--setup-connection (buffer prompt-regexp &optional debug-prompt-regexp debug-preamble-regexp) (with-current-buffer buffer (geiser-con--connection-teardown) (setq geiser-con--debugging-prompt-regexp debug-prompt-regexp) (setq geiser-con--debugging-preamble-regexp debug-preamble-regexp) (setq geiser-con--connection (geiser-con--make-connection buffer)) (set (make-local-variable 'comint-redirect-insert-matching-regexp) t) (setq comint-prompt-regexp (if debug-prompt-regexp (format "\\(%s\\)\\|\\(%s\\)" prompt-regexp debug-prompt-regexp) prompt-regexp)) (add-hook 'comint-redirect-hook 'geiser-con--comint-redirect-hook nil t))) ;;; Requests handling: (defun geiser-con--comint-buffer-form (con) (with-current-buffer (geiser-con--connection-reply-buffer con) (goto-char (point-max)) (if (geiser-con--has-entered-debugger con) `((error (key . geiser-debugger)) (output . ,(buffer-substring (point-min) (point)))) (condition-case nil (progn (goto-char (point-min)) (re-search-forward "((\\(result\\|error\\)\\>") (goto-char (match-beginning 0)) (let ((form (read (current-buffer)))) (if (listp form) form (error "")))) (error `((error (key . geiser-con-error)) (output . ,(buffer-string)))))))) (defun geiser-con--process-next (con) (when (not (geiser-con--connection-current-request con)) (let* ((buffer (geiser-con--connection-buffer con)) (req (geiser-con--connection-pop-request con)) (str (and req (geiser-con--request-string req))) (rbuffer (geiser-con--connection-reply-buffer con))) (when (and buffer (buffer-live-p buffer) req str) (with-current-buffer rbuffer (delete-region (point-min) (point-max))) (set-buffer buffer) (if (geiser-con--is-debugging) (geiser-con--connection-completed con req) (geiser-log--info "<%s>: %s" (geiser-con--request-id req) str) (comint-redirect-send-command (format "%s" str) rbuffer nil t)))))) (defun geiser-con--process-completed-request (req) (let* ((cont (geiser-con--request-continuation req)) (id (geiser-con--request-id req)) (rstr (geiser-con--request-string req)) (buffer (geiser-con--request-buffer req)) (con (geiser-con--request-connection req)) (form (geiser-con--comint-buffer-form con))) (if (not cont) (geiser-log--warn "<%s> Droping result for request %S: %s" id rstr form) (condition-case cerr (with-current-buffer (or buffer (current-buffer)) (funcall cont form) (geiser-con--request-deactivate req) (geiser-log--info "<%s>: processed" id)) (error (geiser-log--error "<%s>: continuation failed %S \n\t%s" id rstr cerr)))) (geiser-con--connection-clean-current-request con))) (defun geiser-con--comint-redirect-hook () (if (not geiser-con--connection) (geiser-log--error "No connection in buffer") (let ((req (geiser-con--connection-current-request geiser-con--connection))) (if (not req) (geiser-log--error "No current request") (geiser-con--process-completed-request req))))) (defadvice comint-redirect-setup (after geiser-con--advice (output-buffer comint-buffer finished-regexp &optional echo)) (with-current-buffer comint-buffer (when geiser-con--eot-regexp (setq comint-redirect-finished-regexp geiser-con--eot-regexp)) (when geiser-con--connection (setq mode-line-process nil)))) (ad-activate 'comint-redirect-setup) ;;; Message sending interface: (defconst geiser-con--error-message "Geiser connection not active") (defvar geiser-connection-timeout 30000 "Time limit, in msecs, blocking on synchronous evaluation requests") (defun geiser-con--send-string/wait (buffer/proc str cont &optional timeout sbuf) (save-current-buffer (let* ((con (geiser-con--get-connection buffer/proc)) (proc (and con (geiser-con--connection-process con)))) (unless proc (error geiser-con--error-message)) (with-current-buffer (geiser-con--connection-buffer con) (when (geiser-con--is-debugging) (error "Geiser REPL is in debug mode"))) (let* ((req (geiser-con--make-request con str cont sbuf)) (id (and req (geiser-con--request-id req))) (time (or timeout geiser-connection-timeout)) (step 100) (waitsecs (/ step 1000.0))) (when id (geiser-con--connection-add-request con req) (geiser-con--process-next con) (condition-case nil (while (and (> time 0) (geiser-con--connection-process con) (not (geiser-con--connection-completed-p con id))) (unless (accept-process-output nil waitsecs nil nil) (geiser-con--process-next con) (setq time (- time step)))) (error (setq time 0))) (or (> time 0) (geiser-con--request-deactivate req) nil)))))) (provide 'geiser-connection) ;;; geiser-connection.el ends here