From: Rahguzar <rahguzar@zohomail.eu>
To: Philip Kaludercic <philipk@posteo.net>
Cc: emacs-devel@gnu.org
Subject: Re: [ELPA] Re: SageMath and Emacs
Date: Mon, 20 May 2024 17:47:06 +0200 [thread overview]
Message-ID: <87wmnotrid.fsf@zohomail.eu> (raw)
In-Reply-To: <87jzjpm0by.fsf@posteo.net> (Philip Kaludercic's message of "Mon, 20 May 2024 07:03:45 +0000")
[-- Attachment #1: Type: text/plain, Size: 541 bytes --]
Hi Philip,
Philip Kaludercic <philipk@posteo.net> writes:
> Philip Kaludercic <philipk@posteo.net> writes:
>
> Ping?
>
Sorry, I got busy with some stuff. I have attached two patches with this
email. The first one provides basic support for SageMath source files
and repls. The second one adds a command to look up documentation of
sage objects. I don't think it uses anything sage specific so it might
make sense to have some such facility for python generally. I tried but
couldn't find something in python.el for it.
Thanks,
Rahguzar
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-Support-for-SageMath.patch --]
[-- Type: text/x-patch, Size: 7303 bytes --]
From 8ff7f1d19efc6362b8d090236e7174bd17aedfa1 Mon Sep 17 00:00:00 2001
From: Rahguzar <rahguzar@zohomail.eu>
Date: Mon, 20 May 2024 16:07:22 +0200
Subject: [PATCH 1/2] Support for SageMath
* lisp/progmodes/sage.el (python):
(sage): New customization group.
(sage-shell-interpreter, sage-shell-interpreter-args):
New custom options.
(sage-shell-eval-setup-code): New variable.
(sage-shell-mode, sage-mode): New major modes.
(sage-mode-map): Keymap for sage-mode.
(run-sage): New function for starting repl.
---
lisp/progmodes/sage.el | 162 +++++++++++++++++++++++++++++++++++++++++
1 file changed, 162 insertions(+)
create mode 100644 lisp/progmodes/sage.el
diff --git a/lisp/progmodes/sage.el b/lisp/progmodes/sage.el
new file mode 100644
index 00000000000..cba3421cbaa
--- /dev/null
+++ b/lisp/progmodes/sage.el
@@ -0,0 +1,162 @@
+;;; sage.el --- SageMath support for Emacs -*- lexical-binding: t; -*-
+
+;; Copyright (C) 2024 Free Software Foundation, Inc.
+
+;; Author: Rahguzar <rahguzar@zohomail.eu>
+;; Maintainer: Rahguzar <rahguzar@zohomail.eu>
+;; Keywords: languages
+
+;; 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 <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;; Major modes for editing Sage files and interacting with sage process. These
+;; are thin wrappers around `python-mode' and `inferior-python-mode'. To
+;; change the behavior of these modes, the facilities provided by `python.el'
+;; can be used in conjunction with `sage-mode-hook' and `sage-shell-mode-hook'.
+
+;;; Code:
+(require 'python)
+
+;;;; Variables
+(defgroup sage nil
+ "Emacs interface to sagemath."
+ :group 'languages)
+
+(defcustom sage-shell-interpreter (executable-find "sage")
+ "Interpreter for sage code.
+Sage version should be at least 9.3."
+ :type 'string)
+
+(defcustom sage-shell-interpreter-args '("--simple-prompt")
+ "Arguments for the sage interpreter.
+Note: \"--simple-promot\" is required for ipython to work."
+ :type '(repeat string))
+
+(defvar sage-shell-eval-setup-code
+ "\
+import warnings
+
+def __PYTHON_EL_eval(source, filename):
+ import ast, sys
+ from sage.repl.preparse import preparse_file
+ if isinstance(source, str):
+ source = preparse_file(source)
+ else:
+ source = preparse_file(source.decode())
+ if sys.version_info[0] == 2:
+ from __builtin__ import compile, eval, globals
+ else:
+ from builtins import compile, eval, globals
+ try:
+ p, e = ast.parse(source, filename), None
+ except SyntaxError:
+ t, v, tb = sys.exc_info()
+ sys.excepthook(t, v, tb.tb_next)
+ return
+ if p.body and isinstance(p.body[-1], ast.Expr):
+ e = p.body.pop()
+ try:
+ g = globals()
+ exec(compile(p, filename, 'exec'), g, g)
+ if e:
+ return eval(compile(ast.Expression(e.value), filename, 'eval'), g, g)
+ except Exception:
+ t, v, tb = sys.exc_info()
+ sys.excepthook(t, v, tb.tb_next)
+"
+ "Code for sending code to a sage process.
+This is essentially same as `python-shell-eval-setup-code' except the addition
+of a preparse step that converts sage code to python code.")
+
+;;;; Sage shell mode
+(define-derived-mode sage-shell-mode inferior-python-mode
+ "Sage Repl" "Execute Sage commands interactively."
+ (setq-local python-shell-eval-setup-code sage-shell-eval-setup-code))
+
+;;;###autoload
+(defun run-sage (&optional dedicated show)
+ "Run an inferior sage process.
+
+The command to run the process is calculated using `sage-shell-interpreter'
+and `sage-shell-interpreter-args'.
+
+When called interactively with `prefix-arg', it allows
+the user to choose whether the interpreter should be DEDICATED to the current
+buffer or project. When numeric prefix arg is other than 0 or 4 do not SHOW.
+
+For a given buffer and same values of DEDICATED, if a process is
+already running for it, it will do nothing. This means that if
+the current buffer is using a global process, the user is still
+able to switch it to use a dedicated one.
+
+Type \\[describe-mode] in the process buffer for a list of commands."
+ (interactive
+ (if current-prefix-arg
+ (list
+ (alist-get (car (read-multiple-choice "Make dedicated process?"
+ '((?b "to buffer")
+ (?p "to project")
+ (?n "no"))))
+ '((?b . buffer) (?p . project)))
+ (= (prefix-numeric-value current-prefix-arg) 4))
+ (list python-shell-dedicated t)))
+ (let* ((proc (python-shell-get-process-name dedicated))
+ (buf (get-buffer-create (format "*%s*" proc))))
+ (unless (get-buffer-process buf)
+ (with-current-buffer buf
+ (let ((inhibit-read-only t))
+ (erase-buffer))
+ (apply #'make-comint-in-buffer proc buf sage-shell-interpreter nil
+ sage-shell-interpreter-args)
+ ;; Ideally we should able to bind `python-shell-interpreter' and
+ ;; `python-shell-interpreter-args' and call `python-run'. However, that
+ ;; runs into problems since `inferior-python-mode' uses
+ ;; `python-shell--interpreter' and `python-shell--interpreter-args'
+ ;; which can only be let bound from within `python.el'. This makes it
+ ;; impossible to call any derived mode of `inferior-python-mode'
+ ;; without dealing with these internal variables.
+ (dlet ((python-shell--interpreter "sage")
+ (python-shell--interpreter-args "--simple-prompt"))
+ (let ((python-shell-prompt-detect-enabled nil)
+ (python-shell-interpreter-interactive-arg "")
+ (python-shell-prompt-regexp "sage: "))
+ (sage-shell-mode))
+ (setq-local python-shell-prompt-detect-enabled nil
+ python-shell-interpreter-interactive-arg ""
+ python-shell-prompt-regexp "sage: "))))
+ (if show (display-buffer buf))
+ proc))
+
+;;;; Sage Mode
+(defvar-keymap sage-mode-map
+ "<remap> <run-python>" #'run-sage)
+
+;;;###autoload
+(define-derived-mode sage-mode python-mode "Sage"
+ :group 'sage
+ (setq-local python-shell-buffer-name "Sage"
+ python-shell-interpreter sage-shell-interpreter
+ python-shell-interpreter-args (combine-and-quote-strings
+ sage-shell-interpreter-args)
+ python-shell-eval-setup-code sage-shell-eval-setup-code))
+
+;;;###autoload
+(cl-pushnew `(,(rx ".sage" eos) . sage-mode) auto-mode-alist :test #'equal)
+
+;;;; Provide
+(provide 'sage)
+;;; sage.el ends here
--
2.45.1
[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #3: 0002-Function-to-look-up-documentation-for-sage-objects.patch --]
[-- Type: text/x-patch, Size: 4409 bytes --]
From 78b125eb925f5a2428aaecc540680cae64f37b67 Mon Sep 17 00:00:00 2001
From: Rahguzar <rahguzar@zohomail.eu>
Date: Mon, 20 May 2024 17:12:17 +0200
Subject: [PATCH 2/2] Function to look up documentation for sage objects
* lisp/progmodes/sage.el
(sage-lookup-doc): New function.
(sage-shell-mode): Setup for sage-lookup-doc.
(sage-mode-map): Add keybinding for sage-lookup-doc.
(sage--redirect-send): Internal function.
---
lisp/progmodes/sage.el | 65 ++++++++++++++++++++++++++++++++++++++++--
1 file changed, 63 insertions(+), 2 deletions(-)
diff --git a/lisp/progmodes/sage.el b/lisp/progmodes/sage.el
index cba3421cbaa..730725924a3 100644
--- a/lisp/progmodes/sage.el
+++ b/lisp/progmodes/sage.el
@@ -82,10 +82,40 @@ sage-shell-eval-setup-code
This is essentially same as `python-shell-eval-setup-code' except the addition
of a preparse step that converts sage code to python code.")
+(defvar sage-shell-doc-setup-code
+ "\
+from IPython.terminal.prompts import Prompts, Token
+class __SAGEPrompt(type(get_ipython().prompts)):
+ redirect_happening = False
+ initial_prompts = get_ipython().prompts
+ def in_prompt_tokens(self, cli=None):
+ if self.redirect_happening:
+ self.redirect_happening = False
+ return [(Token.Prompt,'***** COMINT REDIRECT ENDED *****')]
+ else:
+ return self.initial_prompts.in_prompt_tokens()
+get_ipython().prompts = __SAGEPrompt(get_ipython())
+
+def __SAGE_get_doc(str):
+ In.pop()
+ get_ipython().prompts.redirect_happening = True
+ get_ipython().run_line_magic('pinfo', str)
+"
+ "Code to setup fetching documentation from a Sage process.
+It is needed because the documentation somtimes contains bare prompts which
+trip up the comint redirection machinery. This works around that.")
+
;;;; Sage shell mode
+(defun sage-shell-setup-doc ()
+ "Send `sage-shell-doc-setup-code' to Sage process."
+ (python-shell-send-string-no-output sage-shell-doc-setup-code))
+
(define-derived-mode sage-shell-mode inferior-python-mode
"Sage Repl" "Execute Sage commands interactively."
- (setq-local python-shell-eval-setup-code sage-shell-eval-setup-code))
+ (setq-local python-shell-eval-setup-code sage-shell-eval-setup-code
+ comint-redirect-completed t)
+ (add-hook 'python-shell-first-prompt-hook #'sage-shell-setup-doc)
+ (add-hook 'comint-redirect-filter-functions #'ansi-color-apply nil t))
;;;###autoload
(defun run-sage (&optional dedicated show)
@@ -143,7 +173,8 @@ run-sage
;;;; Sage Mode
(defvar-keymap sage-mode-map
- "<remap> <run-python>" #'run-sage)
+ "<remap> <run-python>" #'run-sage
+ "<remap> <python-eldoc-at-point>" #'sage-lookup-doc)
;;;###autoload
(define-derived-mode sage-mode python-mode "Sage"
@@ -157,6 +188,36 @@ sage-mode
;;;###autoload
(cl-pushnew `(,(rx ".sage" eos) . sage-mode) auto-mode-alist :test #'equal)
+;;;; Documentation
+(defun sage--redirect-send (command output-buffer process echo no-display)
+ "Version of `comint-redirect-send-command-to-process'.
+Which see for COMMAND, OUTPUT-BUFFER, PROCESS, ECHO and NO-DISPLAY.
+The COMMAND must output ***** COMINT REDIRECT ENDED ***** as the last line."
+ (with-current-buffer (process-buffer process)
+ (if (and (python-util-comint-end-of-output-p)
+ comint-redirect-completed)
+ (let ((comint-redirect-perform-sanity-check nil)
+ (comint-prompt-regexp (rx bol "***** COMINT REDIRECT ENDED *****")))
+ (comint-redirect-send-command-to-process
+ command output-buffer process echo no-display))
+ (error "Sage process is busy"))))
+
+(defun sage-lookup-doc (symbol &optional proc display)
+ "Look up documentation for the SYMBOL.
+It is the symbol at point when called interactively. PROC is the sage process.
+If DISPLAY is non-nil, buffer is displayed."
+ (interactive (list (python-info-current-symbol) (python-shell-get-process) t))
+ (let ((buf (get-buffer-create "*Sage Documentation*"))
+ (proc (or proc (python-shell-get-process))))
+ (prog1 buf
+ (with-current-buffer buf
+ (erase-buffer)
+ (setq mode-line-format nil)
+ (font-lock-mode)
+ (visual-line-mode))
+ (sage--redirect-send (format "__SAGE_get_doc('%s')" symbol)
+ buf proc nil (not display)))))
+
;;;; Provide
(provide 'sage)
;;; sage.el ends here
--
2.45.1
next prev parent reply other threads:[~2024-05-20 15:47 UTC|newest]
Thread overview: 8+ messages / expand[flat|nested] mbox.gz Atom feed top
2024-05-09 10:35 SageMath and Emacs Rahguzar
2024-05-09 10:46 ` Eli Zaretskii
2024-05-09 13:01 ` Rahguzar
2024-05-09 12:24 ` [ELPA] " Philip Kaludercic
2024-05-20 7:03 ` Philip Kaludercic
2024-05-20 15:47 ` Rahguzar [this message]
2024-05-21 9:54 ` Augusto Stoffel
2024-05-21 17:52 ` Rahguzar
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
List information: https://www.gnu.org/software/emacs/
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=87wmnotrid.fsf@zohomail.eu \
--to=rahguzar@zohomail.eu \
--cc=emacs-devel@gnu.org \
--cc=philipk@posteo.net \
/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 public inbox
https://git.savannah.gnu.org/cgit/emacs.git
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).