From: Stefan Monnier <monnier@iro.umontreal.ca>
To: Sam Steingold <sds@gnu.org>
Cc: emacs-devel@gnu.org
Subject: Re: over-engineered (and under-standardized) inferior interfaces
Date: Thu, 21 Aug 2014 16:29:35 -0400 [thread overview]
Message-ID: <jwvvbpld20s.fsf-monnier+emacs@gnu.org> (raw)
In-Reply-To: <m2vbpl1wfz.fsf@sds-macbook-pro.home> (Sam Steingold's message of "Thu, 21 Aug 2014 15:20:32 -0400")
> 2. Lack of standardization in interaction and keybindings.
100% agreement. FWIW, I have started a `prog-proc-mode', which is
supposed to be a minor mode used in a programming mode and that makes
the link to an underlying comint mode.
It probably doesn't address all your concerns, but the main motivation
was to try and consolidate/unify all the various key-bindings.
It's currently in use in sml-mode (tho copied and renamed to
sml-prog-proc-mode).
If someone were to try and make use of it in some other major-mode than
sml-mode (which may require making additions to prog-proc-mode since
sml-mode's original support for interaction with an inferior process was
not particularly sophisticated), that would be great.
Stefan
;;; prog-proc.el --- Interacting from a source buffer with an inferior process -*- lexical-binding: t -*-
;; Copyright (C) 2012 Free Software Foundation, Inc.
;; Author: (Stefan Monnier) <monnier@iro.umontreal.ca>
;; This file is part of GNU Emacs.
;; GNU Emacs 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.
;; GNU Emacs 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 GNU Emacs. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; Prog-Proc is a package designed to complement Comint: while Comint was
;; designed originally to handle the needs of inferior process buffers, such
;; as a buffer running a Scheme repl, Comint does not actually provide any
;; functionality that links this process buffer with some source code.
;;
;; That's where Prog-Proc comes into play: it provides the usual commands and
;; key-bindings that lets the user send his code to the underlying repl.
;;; Code:
(eval-when-compile (require 'cl))
(require 'comint)
(require 'compile)
(defvar prog-proc-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [?\C-c ?\C-l] 'prog-proc-load-file)
(define-key map [?\C-c ?\C-c] 'prog-proc-compile)
(define-key map [?\C-c ?\C-z] 'prog-proc-switch-to)
(define-key map [?\C-c ?\C-r] 'prog-proc-send-region)
(define-key map [?\C-c ?\C-b] 'prog-proc-send-buffer)
;; FIXME: Add
;; (define-key map [?\M-C-x] 'prog-proc-send-defun)
;; (define-key map [?\C-x ?\C-e] 'prog-proc-send-last-sexp)
;; FIXME: Add menu. Now, that's trickier because keymap inheritance
;; doesn't play nicely with menus!
map)
"Keymap for `prog-proc-mode'.")
(defvar prog-proc--buffer nil
"The inferior-process buffer to which to send code.")
(make-variable-buffer-local 'prog-proc--buffer)
(defstruct (prog-proc-descriptor
(:constructor prog-proc-make)
(:predicate nil)
(:copier nil))
(name nil :read-only t)
(run nil :read-only t)
(load-cmd nil :read-only t)
(chdir-cmd nil :read-only t)
(command-eol "\n" :read-only t)
(compile-commands-alist nil :read-only t))
(defvar prog-proc-descriptor nil
"Struct containing the various functions to create a new process, ...")
(defmacro prog-proc--prop (prop)
`(,(intern (format "prog-proc-descriptor-%s" prop))
(or prog-proc-descriptor
;; FIXME: Look for available ones and pick one.
(error "Not a `prog-proc' buffer"))))
(defmacro prog-proc--call (method &rest args)
`(funcall (prog-proc--prop ,method) ,@args))
;; The inferior process and his buffer are basically interchangeable.
;; Currently the code takes prog-proc--buffer as the main reference,
;; but all users should either use prog-proc-proc or prog-proc-buffer
;; to find the info.
(defun prog-proc-proc ()
"Return the inferior process for the code in current buffer."
(or (and (buffer-live-p prog-proc--buffer)
(get-buffer-process prog-proc--buffer))
(when (derived-mode-p 'prog-proc-mode 'prog-proc-comint-mode)
(setq prog-proc--buffer (current-buffer))
(get-buffer-process prog-proc--buffer))
(let ((ppd prog-proc-descriptor)
(buf (prog-proc--call run)))
(with-current-buffer buf
(if (and ppd (null prog-proc-descriptor))
(set (make-local-variable 'prog-proc-descriptor) ppd)))
(setq prog-proc--buffer buf)
(get-buffer-process prog-proc--buffer))))
(defun prog-proc-buffer ()
"Return the buffer of the inferior process."
(process-buffer (prog-proc-proc)))
(defun prog-proc-run-repl ()
"Start the read-eval-print process, if it's not running yet."
(interactive)
(ignore (prog-proc-proc)))
(defun prog-proc-switch-to ()
"Switch to the buffer running the read-eval-print process."
(pop-to-buffer (prog-proc-buffer)))
(defun prog-proc-send-string (proc str)
"Send command STR to PROC, with an EOL terminator appended."
(with-current-buffer (process-buffer proc)
;; FIXME: comint-send-string does not pass the string through
;; comint-input-filter-function, so we have to do it by hand.
;; Maybe we should insert the command into the buffer and then call
;; comint-send-input?
(prog-proc-comint-input-filter-function nil)
(comint-send-string proc (concat str (prog-proc--prop command-eol)))))
(defun prog-proc-load-file (file &optional and-go)
"Load FILE into the read-eval-print process.
FILE is the file visited by the current buffer.
If prefix argument AND-GO is used, then we additionally switch
to the buffer where the process is running."
(interactive
(list (or buffer-file-name
(read-file-name "File to load: " nil nil t))
current-prefix-arg))
(comint-check-source file)
(let ((proc (prog-proc-proc)))
(prog-proc-send-string proc (prog-proc--call load-cmd file))
(when and-go (pop-to-buffer (process-buffer proc)))))
(defvar prog-proc--tmp-file nil)
(defun prog-proc-send-region (start end &optional and-go)
"Send the content of the region to the read-eval-print process.
START..END delimit the region; AND-GO if non-nil indicate to additionally
switch to the process's buffer."
(interactive "r\nP")
(if (> start end) (let ((tmp end)) (setq end start) (setq start tmp))
(if (= start end) (error "Nothing to send: the region is empty")))
(let ((proc (prog-proc-proc))
(tmp (make-temp-file "emacs-region")))
(write-region start end tmp nil 'silently)
(when prog-proc--tmp-file
(ignore-errors (delete-file (car prog-proc--tmp-file)))
(set-marker (cdr prog-proc--tmp-file) nil))
(setq prog-proc--tmp-file (cons tmp (copy-marker start)))
(prog-proc-send-string proc (prog-proc--call load-cmd tmp))
(when and-go (pop-to-buffer (process-buffer proc)))))
(defun prog-proc-send-buffer (&optional and-go)
"Send the content of the current buffer to the read-eval-print process.
AND-GO if non-nil indicate to additionally switch to the process's buffer."
(interactive "P")
(prog-proc-send-region (point-min) (point-max) and-go))
(define-derived-mode prog-proc-mode prog-mode "Prog-Proc"
"Major mode for editing source code and interact with an interactive loop."
)
;;; Extended comint-mode for Prog-Proc.
(defun prog-proc-chdir (dir)
"Change the working directory of the inferior process to DIR."
(interactive "DChange to directory: ")
(let ((dir (expand-file-name dir))
(proc (prog-proc-proc)))
(with-current-buffer (process-buffer proc)
(prog-proc-send-string proc (prog-proc--call chdir-cmd dir))
(setq default-directory (file-name-as-directory dir)))))
(defun prog-proc-comint-input-filter-function (str)
;; `compile.el' doesn't know that file location info from errors should be
;; recomputed afresh (without using stale info from earlier compilations).
(compilation-forget-errors) ;Has to run before compilation-fake-loc.
(if (and prog-proc--tmp-file (marker-buffer (cdr prog-proc--tmp-file)))
(compilation-fake-loc (cdr prog-proc--tmp-file)
(car prog-proc--tmp-file)))
str)
(defvar prog-proc-comint-mode-map
(let ((map (make-sparse-keymap)))
(define-key map [?\C-c ?\C-r] 'prog-proc-run-repl)
(define-key map [?\C-c ?\C-l] 'prog-proc-load-file)
map))
(define-derived-mode prog-proc-comint-mode comint-mode "Prog-Proc-Comint"
"Major mode for an inferior process used to run&compile source code."
;; Enable compilation-minor-mode, but only after the child mode is setup
;; since the child-mode might want to add rules to
;; compilation-error-regexp-alist.
(add-hook 'after-change-major-mode-hook #'compilation-minor-mode nil t)
;; The keymap of compilation-minor-mode is too unbearable, so we
;; need to hide most of the bindings.
(let ((map (make-sparse-keymap)))
(dolist (keys '([menu-bar] [follow-link]))
;; Preserve some of the bindings.
(define-key map keys (lookup-key compilation-minor-mode-map keys)))
(add-to-list 'minor-mode-overriding-map-alist
(cons 'compilation-minor-mode map)))
(add-hook 'comint-input-filter-functions
#'prog-proc-comint-input-filter-function nil t))
(defvar prog-proc--compile-command nil
"The command used by default by `prog-proc-compile'.")
(defun prog-proc-compile (command &optional and-go)
"Pass COMMAND to the read-eval-loop process to compile the current file.
You can then use the command \\[next-error] to find the next error message
and move to the source code that caused it.
Interactively, prompts for the command if `compilation-read-command' is
non-nil. With prefix arg, always prompts.
Prefix arg AND-GO also means to switch to the read-eval-loop buffer afterwards."
(interactive
(let* ((dir default-directory)
(cmd "cd \"."))
;; Look for files to determine the default command.
(while (and (stringp dir)
(progn
(dolist (cf (prog-proc--prop compile-commands-alist))
(when (file-exists-p (expand-file-name (cdr cf) dir))
(setq cmd (concat cmd "\"; " (car cf)))
(return nil)))
(not cmd)))
(let ((newdir (file-name-directory (directory-file-name dir))))
(setq dir (unless (equal newdir dir) newdir))
(setq cmd (concat cmd "/.."))))
(setq cmd
(cond
((local-variable-p 'prog-proc--compile-command)
prog-proc--compile-command)
((string-match "^\\s-*cd\\s-+\"\\.\"\\s-*;\\s-*" cmd)
(substring cmd (match-end 0)))
((string-match "^\\s-*cd\\s-+\"\\(\\./\\)" cmd)
(replace-match "" t t cmd 1))
((string-match ";" cmd) cmd)
(t prog-proc--compile-command)))
;; code taken from compile.el
(list (if (or compilation-read-command current-prefix-arg)
(read-from-minibuffer "Compile command: "
cmd nil nil '(compile-history . 1))
cmd))))
;; ;; now look for command's file to determine the directory
;; (setq dir default-directory)
;; (while (and (stringp dir)
;; (dolist (cf (prog-proc--prop compile-commands-alist) t)
;; (when (and (equal cmd (car cf))
;; (file-exists-p (expand-file-name (cdr cf) dir)))
;; (return nil))))
;; (let ((newdir (file-name-directory (directory-file-name dir))))
;; (setq dir (unless (equal newdir dir) newdir))))
;; (setq dir (or dir default-directory))
;; (list cmd dir)))
(set (make-local-variable 'prog-proc--compile-command) command)
(save-some-buffers (not compilation-ask-about-save) nil)
(let ((dir default-directory))
(when (string-match "^\\s-*cd\\s-+\"\\([^\"]+\\)\"\\s-*;" command)
(setq dir (match-string 1 command))
(setq command (replace-match "" t t command)))
(setq dir (expand-file-name dir))
(let ((proc (prog-proc-proc))
(eol (prog-proc--prop command-eol)))
(with-current-buffer (process-buffer proc)
(setq default-directory dir)
(prog-proc-send-string
proc (concat (prog-proc--call chdir-cmd dir)
;; Strip the newline, to avoid adding a prompt.
(if (string-match "\n\\'" eol)
(replace-match " " t t eol) eol)
command))
(when and-go (pop-to-buffer (process-buffer proc)))))))
\f
(provide 'prog-proc)
;;; prog-proc.el ends here
next prev parent reply other threads:[~2014-08-21 20:29 UTC|newest]
Thread overview: 46+ messages / expand[flat|nested] mbox.gz Atom feed top
2014-08-21 19:20 over-engineered (and under-standardized) inferior interfaces Sam Steingold
2014-08-21 20:29 ` Stefan Monnier [this message]
2014-08-21 21:21 ` Jorgen Schaefer
2014-08-21 22:00 ` Dmitry
2014-08-21 22:10 ` Eric S. Raymond
2014-08-22 3:45 ` Stefan Monnier
2014-08-22 5:57 ` Andreas Röhler
2014-08-22 10:55 ` Phillip Lord
2014-08-22 11:41 ` Jorgen Schaefer
2014-08-22 21:29 ` Sam Steingold
2014-08-31 16:30 ` Elias Mårtenson
2014-08-31 20:01 ` Stefan Monnier
2014-08-21 20:43 ` Andreas Schwab
2014-08-21 21:06 ` Sam Steingold
2014-08-22 5:56 ` Andreas Schwab
2014-08-22 6:17 ` Michael Albinus
2014-08-22 21:25 ` Sam Steingold
2014-08-23 15:16 ` Michael Albinus
2014-08-25 17:05 ` Sam Steingold
2014-08-27 6:57 ` Michael Albinus
2014-08-27 10:46 ` Sam Steingold
2014-08-27 11:29 ` Michael Albinus
2014-08-27 12:32 ` Sam Steingold
2014-08-27 12:51 ` Michael Albinus
2014-08-27 13:00 ` Sam Steingold
2014-08-27 13:16 ` Michael Albinus
2014-08-27 13:52 ` Sam Steingold
2014-08-27 13:57 ` Sam Steingold
2014-08-27 18:17 ` Michael Albinus
2014-08-28 1:01 ` Sam Steingold
2014-08-28 8:48 ` Michael Albinus
2014-08-28 16:02 ` Sam Steingold
2014-08-28 18:35 ` Michael Albinus
2014-08-28 19:22 ` Sam Steingold
2014-08-28 19:44 ` Michael Albinus
2014-08-28 19:54 ` Sam Steingold
2014-08-28 20:04 ` Michael Albinus
2014-08-29 15:00 ` Sam Steingold
2014-09-05 14:19 ` Michael Albinus
2014-09-08 13:17 ` Sam Steingold
2014-09-08 14:15 ` Michael Albinus
2014-09-08 14:38 ` Sam Steingold
2014-09-08 14:47 ` Michael Albinus
2014-08-27 14:05 ` Tassilo Horn
2014-08-27 18:13 ` Michael Albinus
2014-08-25 9:23 ` Michael Mattie
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=jwvvbpld20s.fsf-monnier+emacs@gnu.org \
--to=monnier@iro.umontreal.ca \
--cc=emacs-devel@gnu.org \
--cc=sds@gnu.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
Code repositories for project(s) associated with this external index
https://git.savannah.gnu.org/cgit/emacs.git
https://git.savannah.gnu.org/cgit/emacs/org-mode.git
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.