From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.org!not-for-mail From: Stefan Monnier Newsgroups: gmane.emacs.devel Subject: Re: over-engineered (and under-standardized) inferior interfaces Date: Thu, 21 Aug 2014 16:29:35 -0400 Message-ID: References: NNTP-Posting-Host: plane.gmane.org Mime-Version: 1.0 Content-Type: text/plain X-Trace: ger.gmane.org 1408653000 25806 80.91.229.3 (21 Aug 2014 20:30:00 GMT) X-Complaints-To: usenet@ger.gmane.org NNTP-Posting-Date: Thu, 21 Aug 2014 20:30:00 +0000 (UTC) Cc: emacs-devel@gnu.org To: Sam Steingold Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Thu Aug 21 22:29:54 2014 Return-path: Envelope-to: ged-emacs-devel@m.gmane.org Original-Received: from lists.gnu.org ([208.118.235.17]) by plane.gmane.org with esmtp (Exim 4.69) (envelope-from ) id 1XKYzX-0004hN-JD for ged-emacs-devel@m.gmane.org; Thu, 21 Aug 2014 22:29:51 +0200 Original-Received: from localhost ([::1]:33973 helo=lists.gnu.org) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XKYzX-0007t7-6x for ged-emacs-devel@m.gmane.org; Thu, 21 Aug 2014 16:29:51 -0400 Original-Received: from eggs.gnu.org ([2001:4830:134:3::10]:54148) by lists.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XKYzP-0007sk-4z for emacs-devel@gnu.org; Thu, 21 Aug 2014 16:29:48 -0400 Original-Received: from Debian-exim by eggs.gnu.org with spam-scanned (Exim 4.71) (envelope-from ) id 1XKYzJ-0000z0-S6 for emacs-devel@gnu.org; Thu, 21 Aug 2014 16:29:43 -0400 Original-Received: from ironport2-out.teksavvy.com ([206.248.154.181]:29758) by eggs.gnu.org with esmtp (Exim 4.71) (envelope-from ) id 1XKYzJ-0000yZ-Kv; Thu, 21 Aug 2014 16:29:37 -0400 X-IronPort-Anti-Spam-Filtered: true X-IronPort-Anti-Spam-Result: ArYGAIDvNVPAqyKr/2dsb2JhbABZgwY7gw/APYEXF3SCJQEBAQECAVYjBQsLDiYSFBgNJBMJh2gI0hkXjg1tB4Q4BI5UhG2Re4VHg0wh X-IPAS-Result: ArYGAIDvNVPAqyKr/2dsb2JhbABZgwY7gw/APYEXF3SCJQEBAQECAVYjBQsLDiYSFBgNJBMJh2gI0hkXjg1tB4Q4BI5UhG2Re4VHg0wh X-IronPort-AV: E=Sophos;i="4.97,753,1389762000"; d="scan'208";a="87407067" Original-Received: from 192-171-34-171.cpe.pppoe.ca (HELO pastel.home) ([192.171.34.171]) by ironport2-out.teksavvy.com with ESMTP/TLS/DHE-RSA-AES256-SHA; 21 Aug 2014 16:29:36 -0400 Original-Received: by pastel.home (Postfix, from userid 20848) id 5796A62D5A; Thu, 21 Aug 2014 16:29:35 -0400 (EDT) In-Reply-To: (Sam Steingold's message of "Thu, 21 Aug 2014 15:20:32 -0400") User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/24.4.50 (gnu/linux) X-detected-operating-system: by eggs.gnu.org: Genre and OS details not recognized. X-Received-From: 206.248.154.181 X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.14 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane.org@gnu.org Xref: news.gmane.org gmane.emacs.devel:173791 Archived-At: > 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) ;; 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 . ;;; 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))))))) (provide 'prog-proc) ;;; prog-proc.el ends here