unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
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


  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).