From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Stefan Monnier Newsgroups: gmane.emacs.devel Subject: Re: Standardizing more key bindings? Date: Sun, 27 Sep 2020 11:57:59 -0400 Message-ID: References: Mime-Version: 1.0 Content-Type: text/plain Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="21891"; mail-complaints-to="usenet@ciao.gmane.io" User-Agent: Gnus/5.13 (Gnus v5.13) Emacs/28.0.50 (gnu/linux) Cc: emacs-devel To: Thibaut Verron Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Sun Sep 27 17:59:21 2020 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1kMZ57-0005Zj-LA for ged-emacs-devel@m.gmane-mx.org; Sun, 27 Sep 2020 17:59:21 +0200 Original-Received: from localhost ([::1]:36146 helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1kMZ56-0002kq-MF for ged-emacs-devel@m.gmane-mx.org; Sun, 27 Sep 2020 11:59:20 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]:58612) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kMZ3v-0001v0-2G for emacs-devel@gnu.org; Sun, 27 Sep 2020 11:58:07 -0400 Original-Received: from mailscanner.iro.umontreal.ca ([132.204.25.50]:17961) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1kMZ3s-0007NT-0k for emacs-devel@gnu.org; Sun, 27 Sep 2020 11:58:06 -0400 Original-Received: from pmg1.iro.umontreal.ca (localhost.localdomain [127.0.0.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id A858710034E; Sun, 27 Sep 2020 11:58:02 -0400 (EDT) Original-Received: from mail01.iro.umontreal.ca (unknown [172.31.2.1]) by pmg1.iro.umontreal.ca (Proxmox) with ESMTP id 1D8A7100271; Sun, 27 Sep 2020 11:58:00 -0400 (EDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=iro.umontreal.ca; s=mail; t=1601222280; bh=GhurhL3YMyantSnDR6zVYpVqjR8JBnhHKfsb2jq3i/Y=; h=From:To:Cc:Subject:References:Date:In-Reply-To:From; b=dqgMAxGDZ0DNOqSnc3uoov1HHIJxla1ywhnMLvoVDtC0ElPjJFrZq3rzhUpvtwcdE DoqwC2RanE0QqbJXnqgUY9X82Saf7LiNJkkUZTWjTZ9vuzWmZB5B3WIze5CkNuxk/v G+59uEZAAune2pkZ7ps9rTxk5R7EseD6cccWjsojY3EVdFRCZ+oYFdXS/MXew8Mh9d lB9h21fEZHoSC7VuaTBEkEjEHhr3edQ26tj0z69RKCYpKYini/Y/E+IS5ySSutiBk/ 7lq6farALIUfu6JhCSV4RODLY1bOAlU2D/CWb6YDeBEZWqzjIElPVg4GVevZvuNnlO UcAse0TEbAarA== Original-Received: from alfajor (unknown [45.72.232.131]) by mail01.iro.umontreal.ca (Postfix) with ESMTPSA id DD5A21202C1; Sun, 27 Sep 2020 11:57:59 -0400 (EDT) In-Reply-To: (Thibaut Verron's message of "Sun, 27 Sep 2020 11:31:03 +0200") Received-SPF: pass client-ip=132.204.25.50; envelope-from=monnier@iro.umontreal.ca; helo=mailscanner.iro.umontreal.ca X-detected-operating-system: by eggs.gnu.org: First seen = 2020/09/27 11:46:31 X-ACL-Warn: Detected OS = Linux 2.2.x-3.x [generic] X-Spam_score_int: -42 X-Spam_score: -4.3 X-Spam_bar: ---- X-Spam_report: (-4.3 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, RCVD_IN_DNSWL_MED=-2.3, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.23 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-mx.org@gnu.org Original-Sender: "Emacs-devel" Xref: news.gmane.io gmane.emacs.devel:256543 Archived-At: > I seem to recall Stefan bringing up such a discussion a few years ago, > the goal then being to simplify setting up a comint buffer. FWIW, here's the code I have for that, written about 5 years ago and never actually used, so take it with a large grain of salt. 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-lib)) (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-local prog-proc--buffer nil "The inferior-process buffer to which to send code.") (defmacro prog-proc-define-token (name &optional parent) ;; FIXME: This use of cl-defstruct ends up defining needlessly the ;; predicate, the constructor and their respective inliners. ;; `(progn ;; (cl-defstruct (,name ;; (:predicate nil) ;; (:copier nil) ;; (:constructor nil) ;; (:constructor ,name) ;; ,@(when parent `((:include ,parent))))) ;; (defconst ,name (,name))) (let ((tag-sym (intern (format "cl-struct-%S" name))) (children-sym (intern (format "cl-struct-%S-tags" name)))) `(progn (defvar ,children-sym) (eval-and-compile (cl-struct-define ',name nil ',(or parent ;; FIXME: Make a "token" parent. 'cl-structure-object) nil nil '((cl-tag-slot)) ',children-sym ',tag-sym t)) (defconst ,name (vector ',tag-sym))))) (cl-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 (cl-dolist (cf (prog-proc--prop compile-commands-alist)) (when (file-exists-p (expand-file-name (cdr cf) dir)) (setq cmd (concat cmd "\"; " (car cf))) (cl-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