2012-05-07 14:41:15 +02:00
|
|
|
|
;;; ein-utils.el --- Utility module
|
|
|
|
|
|
|
|
|
|
;; Copyright (C) 2012- Takafumi Arakaki
|
|
|
|
|
|
2012-07-01 20:18:05 +02:00
|
|
|
|
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
;; This file is NOT part of GNU Emacs.
|
|
|
|
|
|
|
|
|
|
;; ein-utils.el 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.
|
|
|
|
|
|
|
|
|
|
;; ein-utils.el 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 ein-utils.el. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
|
|
|
|
|
|
;;; Commentary:
|
|
|
|
|
|
|
|
|
|
;;
|
|
|
|
|
|
|
|
|
|
;;; Code:
|
|
|
|
|
|
|
|
|
|
(eval-when-compile (require 'cl))
|
|
|
|
|
(require 'json)
|
|
|
|
|
|
2012-08-03 23:52:34 +02:00
|
|
|
|
;; Optional dependency on tramp:
|
|
|
|
|
(declare-function tramp-make-tramp-file-name "tramp")
|
|
|
|
|
(declare-function tramp-file-name-localname "tramp")
|
|
|
|
|
(declare-function tramp-dissect-file-name "tramp")
|
|
|
|
|
|
2012-05-19 14:34:00 +02:00
|
|
|
|
(defgroup ein nil
|
|
|
|
|
"IPython notebook client in Emacs"
|
|
|
|
|
:group 'applications
|
|
|
|
|
:prefix "ein:")
|
|
|
|
|
|
2012-07-14 16:22:15 +02:00
|
|
|
|
|
|
|
|
|
;;; Configuration
|
|
|
|
|
|
2012-05-19 14:34:00 +02:00
|
|
|
|
(defcustom ein:url-or-port '(8888)
|
|
|
|
|
"List of default url-or-port values.
|
|
|
|
|
This will be used for completion. So put your IPython servers.
|
|
|
|
|
You can connect to servers not in this list \(but you will need
|
|
|
|
|
to type every time)."
|
|
|
|
|
:type '(repeat (choice (integer :tag "Port number" 8888)
|
|
|
|
|
(string :tag "URL" "http://127.0.0.1:8888")))
|
|
|
|
|
:group 'ein)
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-06-15 21:46:55 +02:00
|
|
|
|
(defcustom ein:default-url-or-port nil
|
|
|
|
|
"Default URL or port. This should be your main IPython
|
|
|
|
|
Notebook server."
|
|
|
|
|
:type '(choice (integer :tag "Port number" 8888)
|
|
|
|
|
(string :tag "URL" "http://127.0.0.1:8888")
|
|
|
|
|
(const :tag "First value of `ein:url-or-port'" nil))
|
|
|
|
|
:group 'ein)
|
|
|
|
|
|
2012-07-14 16:23:25 +02:00
|
|
|
|
(defcustom ein:scratch-notebook-name-template "_scratch_%Y-%m-%d-%H%M%S_"
|
2012-07-14 16:43:42 +02:00
|
|
|
|
"Template of scratch notebook name.
|
|
|
|
|
This value is used from `ein:notebooklist-new-scratch-notebook'
|
|
|
|
|
and `ein:notebook-rename-to-scratch-command'. This must be a
|
|
|
|
|
format string which can be passed to `format-time-string'."
|
2012-07-14 16:23:25 +02:00
|
|
|
|
:type '(string :tag "Format string")
|
|
|
|
|
:group 'ein)
|
|
|
|
|
|
2012-08-03 22:30:20 +02:00
|
|
|
|
(defcustom ein:filename-translations nil
|
|
|
|
|
"Convert file paths between Emacs and Python process.
|
|
|
|
|
|
|
|
|
|
This value can take these form:
|
|
|
|
|
|
|
|
|
|
alist
|
|
|
|
|
Its key specifies URL-OR-PORT and value must be a list of two
|
|
|
|
|
functions: (TO-PYTHON FROM-PYTHON). Key (URL-OR-PORT) can be
|
|
|
|
|
string (URL), integer (port), or `default' (symbol). The
|
|
|
|
|
value of `default' is used when other key does not much.
|
|
|
|
|
function
|
|
|
|
|
Called with an argument URL-OR-PORT (integer or string).
|
|
|
|
|
This function must return a list of two functions:
|
|
|
|
|
(TO-PYTHON FROM-PYTHON).
|
|
|
|
|
|
|
|
|
|
Here, the functions TO-PYTHON and FROM-PYTHON are defined as:
|
|
|
|
|
|
|
|
|
|
TO-PYTHON
|
|
|
|
|
A function which converts a file name (returned by
|
|
|
|
|
`buffer-file-name') to the one Python understands.
|
|
|
|
|
FROM-PYTHON
|
|
|
|
|
A function which converts a file path returned by
|
2012-08-03 23:06:46 +02:00
|
|
|
|
Python process to the one Emacs understands.
|
|
|
|
|
|
2012-08-04 04:16:05 +02:00
|
|
|
|
Use `ein:tramp-create-filename-translator' to easily generate the
|
2012-08-03 23:06:46 +02:00
|
|
|
|
pair of TO-PYTHON and FROM-PYTHON."
|
2012-08-03 22:30:20 +02:00
|
|
|
|
;; I've got the idea from `slime-filename-translations'.
|
|
|
|
|
:type '(choice
|
|
|
|
|
(alist :tag "Translations mapping"
|
|
|
|
|
:key-type (choice :tag "URL or PORT"
|
|
|
|
|
(string :tag "URL" "http://127.0.0.1:8888")
|
|
|
|
|
(integer :tag "PORT" 8888)
|
|
|
|
|
(const default))
|
|
|
|
|
:value-type (list (function :tag "TO-PYTHON")
|
|
|
|
|
(function :tag "FROM-PYTHON")))
|
|
|
|
|
(function :tag "Translations getter"))
|
|
|
|
|
:group 'ein)
|
|
|
|
|
|
2012-07-14 16:22:15 +02:00
|
|
|
|
|
|
|
|
|
;;; Macros and core functions/variables
|
|
|
|
|
|
2012-06-15 21:46:55 +02:00
|
|
|
|
(defun ein:default-url-or-port ()
|
|
|
|
|
(or ein:default-url-or-port (car ein:url-or-port) 8888))
|
|
|
|
|
|
2012-07-14 16:30:56 +02:00
|
|
|
|
(defun ein:scratch-notebook-name ()
|
|
|
|
|
"Generate new scratch notebook name based on `current-time' and
|
|
|
|
|
`ein:scratch-notebook-name-template'."
|
|
|
|
|
(format-time-string ein:scratch-notebook-name-template (current-time)))
|
|
|
|
|
|
2012-06-03 17:30:16 +02:00
|
|
|
|
(defvar ein:source-dir (file-name-directory load-file-name))
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
(defmacro ein:aif (test-form then-form &rest else-forms)
|
2012-05-17 21:26:55 +02:00
|
|
|
|
"Anaphoric IF. Adapted from `e2wm:aif'."
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(declare (debug (form form &rest form)))
|
|
|
|
|
`(let ((it ,test-form))
|
|
|
|
|
(if it ,then-form ,@else-forms)))
|
|
|
|
|
(put 'ein:aif 'lisp-indent-function 2)
|
|
|
|
|
|
|
|
|
|
(defmacro ein:aand (test &rest rest)
|
2012-05-17 21:26:55 +02:00
|
|
|
|
"Anaphoric AND. Adapted from `e2wm:aand'."
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(declare (debug (form &rest form)))
|
|
|
|
|
`(let ((it ,test))
|
|
|
|
|
(if it ,(if rest (macroexpand-all `(ein:aand ,@rest)) 'it))))
|
|
|
|
|
|
2012-08-14 20:02:29 +02:00
|
|
|
|
(defmacro ein:and-let* (bindings &rest form)
|
|
|
|
|
"Gauche's `and-let*'."
|
|
|
|
|
(declare (debug (((symbolp form)) &rest form))
|
|
|
|
|
(indent 1))
|
|
|
|
|
(if (null bindings)
|
|
|
|
|
`(progn ,@form)
|
|
|
|
|
(let* ((head (car bindings))
|
|
|
|
|
(tail (cdr bindings))
|
|
|
|
|
(rest (macroexpand-all `(ein:and-let* ,tail ,@form))))
|
|
|
|
|
(cond
|
|
|
|
|
((symbolp head) `(if ,head ,rest))
|
|
|
|
|
((= (length head) 1) `(if ,(car head) ,rest))
|
|
|
|
|
(t `(let (,head) (if ,(car head) ,rest)))))))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-12 22:55:06 +02:00
|
|
|
|
(defmacro ein:deflocal (name &optional initvalue docstring)
|
|
|
|
|
"Define permanent buffer local variable named NAME.
|
|
|
|
|
INITVALUE and DOCSTRING are passed to `defvar'."
|
2012-05-13 06:04:08 +02:00
|
|
|
|
(declare (indent defun)
|
|
|
|
|
(doc-string 3))
|
2012-05-12 22:55:06 +02:00
|
|
|
|
`(progn
|
|
|
|
|
(defvar ,name ,initvalue ,docstring)
|
|
|
|
|
(make-variable-buffer-local ',name)
|
|
|
|
|
(put ',name 'permanent-local t)))
|
|
|
|
|
|
2012-05-14 03:06:41 +02:00
|
|
|
|
(defmacro ein:with-read-only-buffer (buffer &rest body)
|
|
|
|
|
(declare (indent 1))
|
|
|
|
|
`(with-current-buffer ,buffer
|
|
|
|
|
(setq buffer-read-only t)
|
|
|
|
|
(save-excursion
|
|
|
|
|
(let ((inhibit-read-only t))
|
|
|
|
|
,@body))))
|
|
|
|
|
|
2012-08-19 12:44:00 +02:00
|
|
|
|
(defmacro ein:with-live-buffer (buffer &rest body)
|
|
|
|
|
"Execute BODY in BUFFER if BUFFER is alive."
|
|
|
|
|
(declare (indent 1) (debug t))
|
|
|
|
|
`(when (buffer-live-p ,buffer)
|
|
|
|
|
(with-current-buffer ,buffer
|
|
|
|
|
,@body)))
|
|
|
|
|
|
2012-05-16 04:15:10 +02:00
|
|
|
|
(defvar ein:dotty-syntax-table
|
|
|
|
|
(let ((table (make-syntax-table c-mode-syntax-table)))
|
|
|
|
|
(modify-syntax-entry ?. "w" table)
|
|
|
|
|
(modify-syntax-entry ?_ "w" table)
|
|
|
|
|
table)
|
|
|
|
|
"Adapted from `python-dotty-syntax-table'.")
|
|
|
|
|
|
2012-05-16 05:04:30 +02:00
|
|
|
|
(defun ein:object-at-point ()
|
2012-06-08 17:33:54 +02:00
|
|
|
|
"Return dotty.words.at.point.
|
2012-06-12 23:22:41 +02:00
|
|
|
|
When region is active, text in region is returned after trimmed
|
|
|
|
|
white spaces, newlines and dots.
|
2012-06-08 17:33:54 +02:00
|
|
|
|
When object is not found at the point, return the object just
|
|
|
|
|
before previous opening parenthesis."
|
|
|
|
|
;; For auto popup tooltip (or something like eldoc), probably it is
|
|
|
|
|
;; better to return function (any word before "("). I should write
|
|
|
|
|
;; another function or add option to this function when the auto
|
|
|
|
|
;; popup tooltip is implemented.
|
2012-06-12 23:22:41 +02:00
|
|
|
|
(if (region-active-p)
|
|
|
|
|
(ein:trim (buffer-substring (region-beginning) (region-end))
|
|
|
|
|
"\\s-\\|\n\\|\\.")
|
|
|
|
|
(save-excursion
|
|
|
|
|
(with-syntax-table ein:dotty-syntax-table
|
|
|
|
|
(ein:aif (thing-at-point 'word)
|
|
|
|
|
it
|
|
|
|
|
(unless (looking-at "(")
|
|
|
|
|
(search-backward "(" (point-at-bol) t))
|
|
|
|
|
(thing-at-point 'word))))))
|
2012-05-16 05:04:30 +02:00
|
|
|
|
|
2012-08-19 21:36:35 +02:00
|
|
|
|
(defun ein:object-at-point-or-error ()
|
|
|
|
|
(or (ein:object-at-point) (error "No object found at the point")))
|
|
|
|
|
|
2012-05-13 02:51:47 +02:00
|
|
|
|
|
|
|
|
|
;;; URL utils
|
|
|
|
|
|
|
|
|
|
(defvar ein:url-localhost-template "http://127.0.0.1:%s")
|
|
|
|
|
|
|
|
|
|
(defun ein:url (url-or-port &rest paths)
|
|
|
|
|
(loop with url = (if (integerp url-or-port)
|
|
|
|
|
(format ein:url-localhost-template url-or-port)
|
|
|
|
|
url-or-port)
|
|
|
|
|
for p in paths
|
|
|
|
|
do (setq url (concat (ein:trim-right url "/")
|
|
|
|
|
"/"
|
|
|
|
|
(ein:trim-left p "/")))
|
|
|
|
|
finally return url))
|
|
|
|
|
|
2012-05-13 06:51:26 +02:00
|
|
|
|
(defun ein:url-no-cache (url)
|
|
|
|
|
"Imitate `cache=false' of `jQuery.ajax'.
|
|
|
|
|
See: http://api.jquery.com/jQuery.ajax/"
|
|
|
|
|
(concat url (format-time-string "?_=%s")))
|
|
|
|
|
|
2012-05-13 02:51:47 +02:00
|
|
|
|
|
|
|
|
|
;;; JSON utils
|
2012-05-12 22:55:06 +02:00
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defmacro ein:with-json-setting (&rest body)
|
|
|
|
|
`(let ((json-object-type 'plist)
|
|
|
|
|
(json-array-type 'list))
|
|
|
|
|
,@body))
|
|
|
|
|
|
|
|
|
|
(defun ein:json-read ()
|
|
|
|
|
"Read json from `url-retrieve'-ed buffer.
|
|
|
|
|
|
|
|
|
|
* `json-object-type' is `plist'. This is mainly for readability.
|
|
|
|
|
* `json-array-type' is `list'. Notebook data is edited locally thus
|
|
|
|
|
data type must be edit-friendly. `vector' type is not."
|
|
|
|
|
(goto-char (point-max))
|
|
|
|
|
(backward-sexp)
|
|
|
|
|
(ein:with-json-setting
|
|
|
|
|
(json-read)))
|
|
|
|
|
|
|
|
|
|
(defun ein:json-read-from-string (string)
|
|
|
|
|
(ein:with-json-setting
|
|
|
|
|
(json-read-from-string string)))
|
|
|
|
|
|
2012-08-11 14:10:41 +02:00
|
|
|
|
(defun ein:json-encode-char (char)
|
|
|
|
|
"Fixed `json-encode-char'."
|
|
|
|
|
(setq char (json-encode-char0 char 'ucs))
|
|
|
|
|
(let ((control-char (car (rassoc char json-special-chars))))
|
|
|
|
|
(cond
|
|
|
|
|
;; Special JSON character (\n, \r, etc.)
|
|
|
|
|
(control-char
|
|
|
|
|
(format "\\%c" control-char))
|
|
|
|
|
;; ASCIIish printable character
|
|
|
|
|
((and (> char 31) (< char 160)) ; s/161/160/
|
|
|
|
|
(format "%c" char))
|
|
|
|
|
;; Fallback: UCS code point in \uNNNN form
|
|
|
|
|
(t
|
|
|
|
|
(format "\\u%04x" char)))))
|
|
|
|
|
|
|
|
|
|
(defadvice json-encode-char (around ein:json-encode-char (char) activate)
|
|
|
|
|
"Replace `json-encode-char' with `ein:json-encode-char'."
|
|
|
|
|
(setq ad-return-value (ein:json-encode-char char)))
|
|
|
|
|
|
2012-08-16 17:03:53 +02:00
|
|
|
|
|
|
|
|
|
;;; EWOC
|
|
|
|
|
|
|
|
|
|
(defun ein:ewoc-create (pretty-printer &optional header footer nosep)
|
|
|
|
|
"Do nothing wrapper of `ewoc-create' to provide better error message."
|
|
|
|
|
(condition-case nil
|
|
|
|
|
(ewoc-create pretty-printer header footer nosep)
|
|
|
|
|
((debug wrong-number-of-arguments)
|
|
|
|
|
(ein:display-warning "Incompatible EOWC version.
|
|
|
|
|
The version of ewoc.el you are using is too old for EIN.
|
|
|
|
|
Please install the newer version.
|
|
|
|
|
See also: https://github.com/tkf/emacs-ipython-notebook/issues/49")
|
|
|
|
|
(error "Incompatible EOWC version."))))
|
|
|
|
|
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
2012-06-11 19:30:45 +02:00
|
|
|
|
;;; Text property
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-20 22:19:00 +02:00
|
|
|
|
(defun ein:propertize-read-only (string &rest properties)
|
|
|
|
|
(apply #'propertize string 'read-only t 'front-sticky t properties))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-20 22:19:00 +02:00
|
|
|
|
(defun ein:insert-read-only (string &rest properties)
|
|
|
|
|
(insert (apply #'ein:propertize-read-only string properties)))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
|
|
|
|
;;; String manipulation
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-13 02:51:47 +02:00
|
|
|
|
(defun ein:trim (string &optional regexp)
|
|
|
|
|
(ein:trim-left (ein:trim-right string regexp) regexp))
|
|
|
|
|
|
|
|
|
|
(defun ein:trim-left (string &optional regexp)
|
|
|
|
|
(unless regexp (setq regexp "\\s-\\|\n"))
|
|
|
|
|
(ein:trim-regexp string (format "^\\(%s\\)+" regexp)))
|
|
|
|
|
|
|
|
|
|
(defun ein:trim-right (string &optional regexp)
|
|
|
|
|
(unless regexp (setq regexp "\\s-\\|\n"))
|
|
|
|
|
(ein:trim-regexp string (format "\\(%s\\)+$" regexp)))
|
|
|
|
|
|
|
|
|
|
(defun ein:trim-regexp (string regexp)
|
|
|
|
|
(if (string-match regexp string)
|
|
|
|
|
(replace-match "" t t string)
|
|
|
|
|
string))
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
2012-05-23 13:24:21 +02:00
|
|
|
|
(defun ein:trim-indent (string)
|
|
|
|
|
"Strip uniform amount of indentation from lines in STRING."
|
|
|
|
|
(let* ((lines (split-string string "\n"))
|
|
|
|
|
(indent
|
|
|
|
|
(let ((lens
|
|
|
|
|
(loop for line in lines
|
|
|
|
|
for stripped = (ein:trim-left line)
|
|
|
|
|
unless (equal stripped "")
|
|
|
|
|
collect (- (length line) (length stripped)))))
|
|
|
|
|
(if lens (apply #'ein:min lens) 0)))
|
|
|
|
|
(trimmed
|
|
|
|
|
(loop for line in lines
|
|
|
|
|
if (> (length line) indent)
|
|
|
|
|
collect (ein:trim-right (substring line indent))
|
|
|
|
|
else
|
|
|
|
|
collect line)))
|
|
|
|
|
(ein:join-str "\n" trimmed)))
|
|
|
|
|
|
2012-05-17 14:03:45 +02:00
|
|
|
|
(defun ein:join-str (sep strings)
|
|
|
|
|
(mapconcat 'identity strings sep))
|
|
|
|
|
|
2012-05-17 20:12:40 +02:00
|
|
|
|
(defun ein:join-path (paths)
|
|
|
|
|
(mapconcat 'file-name-as-directory paths ""))
|
|
|
|
|
|
2012-07-25 18:51:02 +02:00
|
|
|
|
(defun ein:string-fill-paragraph (string &optional justify)
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(insert string)
|
|
|
|
|
(goto-char (point-min))
|
|
|
|
|
(fill-paragraph justify)
|
|
|
|
|
(buffer-string)))
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defmacro ein:case-equal (str &rest clauses)
|
|
|
|
|
"Similar to `case' but comparison is done by `equal'.
|
|
|
|
|
Adapted from twittering-mode.el's `case-string'."
|
|
|
|
|
(declare (indent 1))
|
|
|
|
|
`(cond
|
|
|
|
|
,@(mapcar
|
|
|
|
|
(lambda (clause)
|
|
|
|
|
(let ((keylist (car clause))
|
|
|
|
|
(body (cdr clause)))
|
|
|
|
|
`(,(if (listp keylist)
|
|
|
|
|
`(or ,@(mapcar (lambda (key) `(equal ,str ,key))
|
|
|
|
|
keylist))
|
|
|
|
|
't)
|
|
|
|
|
,@body)))
|
|
|
|
|
clauses)))
|
|
|
|
|
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
2012-06-11 19:30:45 +02:00
|
|
|
|
;;; Misc
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defun ein:plist-iter (plist)
|
2012-05-12 22:57:49 +02:00
|
|
|
|
"Return list of (key . value) in PLIST."
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(loop for p in plist
|
|
|
|
|
for i from 0
|
|
|
|
|
for key-p = (= (% i 2) 0)
|
|
|
|
|
with key = nil
|
|
|
|
|
if key-p do (setq key p)
|
|
|
|
|
else collect `(,key . ,p)))
|
|
|
|
|
|
2012-06-26 17:45:51 +02:00
|
|
|
|
(defun ein:hash-keys (table)
|
|
|
|
|
(let (keys)
|
|
|
|
|
(maphash (lambda (k v) (push k keys)) table)
|
|
|
|
|
keys))
|
|
|
|
|
|
|
|
|
|
(defun ein:hash-vals (table)
|
|
|
|
|
(let (vals)
|
|
|
|
|
(maphash (lambda (k v) (push v vals)) table)
|
|
|
|
|
vals))
|
|
|
|
|
|
2012-05-21 04:29:43 +02:00
|
|
|
|
(defun ein:filter (predicate sequence)
|
|
|
|
|
(loop for item in sequence
|
|
|
|
|
when (funcall predicate item)
|
|
|
|
|
collect item))
|
|
|
|
|
|
2012-06-13 00:18:12 +02:00
|
|
|
|
(defun ein:clip-list (list first last)
|
|
|
|
|
"Return elements in region of the LIST specified by FIRST and LAST element.
|
|
|
|
|
|
|
|
|
|
Example::
|
|
|
|
|
|
|
|
|
|
(ein:clip-list '(1 2 3 4 5 6) 2 4) ;=> (2 3 4)"
|
|
|
|
|
(loop for elem in list
|
|
|
|
|
with clipped
|
|
|
|
|
with in-region-p = nil
|
|
|
|
|
when (eq elem first)
|
|
|
|
|
do (setq in-region-p t)
|
|
|
|
|
when in-region-p
|
|
|
|
|
do (push elem clipped)
|
|
|
|
|
when (eq elem last)
|
|
|
|
|
return (reverse clipped)))
|
|
|
|
|
|
2012-05-16 19:15:48 +02:00
|
|
|
|
(defun ein:get-value (obj)
|
|
|
|
|
"Get value from obj if it is a variable or function."
|
|
|
|
|
(cond
|
|
|
|
|
((not (symbolp obj)) obj)
|
|
|
|
|
((boundp obj) (eval obj))
|
|
|
|
|
((fboundp obj) (funcall obj))))
|
|
|
|
|
|
2012-05-14 21:03:23 +02:00
|
|
|
|
(defun ein:choose-setting (symbol value)
|
|
|
|
|
"Choose setting in stored in SYMBOL based on VALUE.
|
|
|
|
|
The value of SYMBOL can be string, alist or function."
|
|
|
|
|
(let ((setting (eval symbol)))
|
|
|
|
|
(cond
|
|
|
|
|
((stringp setting) setting)
|
|
|
|
|
((functionp setting) (funcall setting value))
|
|
|
|
|
((listp setting)
|
2012-05-16 19:15:48 +02:00
|
|
|
|
(ein:get-value (or (assoc-default value setting)
|
|
|
|
|
(assoc-default 'default setting))))
|
2012-05-14 21:03:23 +02:00
|
|
|
|
(t (error "Unsupported type of `%s': %s" symbol (type-of setting))))))
|
|
|
|
|
|
2012-05-10 00:26:47 +02:00
|
|
|
|
(defmacro ein:setf-default (place val)
|
|
|
|
|
"Set VAL to PLACE using `setf' if the value of PLACE is `nil'."
|
|
|
|
|
`(unless ,place
|
|
|
|
|
(setf ,place ,val)))
|
|
|
|
|
|
2012-05-22 13:34:38 +02:00
|
|
|
|
(defun ein:funcall-packed (func-arg &rest args)
|
|
|
|
|
"Call \"packed\" function.
|
|
|
|
|
FUNC-ARG is a `cons' of the form: (FUNC ARG).
|
|
|
|
|
FUNC is called as (apply FUNC ARG ARGS)."
|
|
|
|
|
(apply (car func-arg) (cdr func-arg) args))
|
|
|
|
|
|
2012-05-15 23:04:18 +02:00
|
|
|
|
(defun ein:eval-if-bound (symbol)
|
|
|
|
|
(if (boundp symbol) (eval symbol)))
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defun ein:remove-by-index (list indices)
|
|
|
|
|
"Remove elements from LIST if its index is in INDICES.
|
|
|
|
|
NOTE: This function creates new list."
|
|
|
|
|
(loop for l in list
|
|
|
|
|
for i from 0
|
|
|
|
|
when (not (memq i indices))
|
|
|
|
|
collect l))
|
|
|
|
|
|
2012-05-23 13:24:21 +02:00
|
|
|
|
(defun ein:min (x &rest xs)
|
|
|
|
|
(loop for y in xs if (< y x) do (setq x y))
|
|
|
|
|
x)
|
|
|
|
|
|
2012-07-12 02:56:23 +02:00
|
|
|
|
(defun ein:do-nothing (&rest -ignore-)
|
|
|
|
|
"A function which can take any number of variables and do nothing.")
|
|
|
|
|
|
2012-06-02 18:46:02 +02:00
|
|
|
|
(defun ein:ask-choice-char (prompt choices)
|
|
|
|
|
"Show PROMPT and read one of acceptable key specified as CHOICES."
|
|
|
|
|
(let ((char-list (loop for i from 0 below (length choices)
|
|
|
|
|
collect (elt choices i)))
|
|
|
|
|
(answer 'recenter))
|
|
|
|
|
(while
|
|
|
|
|
(let ((key
|
|
|
|
|
(let ((cursor-in-echo-area t))
|
|
|
|
|
(read-key (propertize (if (eq answer 'recenter)
|
|
|
|
|
prompt
|
|
|
|
|
(concat "Please choose answer from"
|
|
|
|
|
(format " %s. " choices)
|
|
|
|
|
prompt))
|
|
|
|
|
'face 'minibuffer-prompt)))))
|
|
|
|
|
(setq answer (lookup-key query-replace-map (vector key) t))
|
|
|
|
|
(cond
|
|
|
|
|
((memq key char-list) (setq answer key) nil)
|
|
|
|
|
((eq answer 'recenter) (recenter) t)
|
|
|
|
|
((memq answer '(exit-prefix quit)) (signal 'quit nil) t)
|
|
|
|
|
(t t)))
|
|
|
|
|
(ding)
|
|
|
|
|
(discard-input))
|
|
|
|
|
answer))
|
|
|
|
|
|
2012-06-11 19:17:18 +02:00
|
|
|
|
|
|
|
|
|
(defun ein:truncate-lines-on ()
|
|
|
|
|
"Set `truncate-lines' on (set it to `t')."
|
|
|
|
|
(setq truncate-lines t))
|
|
|
|
|
|
2012-08-12 17:33:35 +02:00
|
|
|
|
|
|
|
|
|
;;; Emacs utilities
|
|
|
|
|
|
2012-06-20 23:05:14 +02:00
|
|
|
|
(defun ein:byte-compile-ein ()
|
|
|
|
|
"Byte compile EIN files."
|
|
|
|
|
(interactive)
|
|
|
|
|
(let* ((files (directory-files ein:source-dir 'full "^ein-.*\\.el$"))
|
|
|
|
|
(errors (ein:filter
|
|
|
|
|
'identity
|
|
|
|
|
(mapcar (lambda (f) (unless (byte-compile-file f) f))
|
|
|
|
|
files))))
|
|
|
|
|
(ein:aif errors
|
|
|
|
|
(error "Got %s errors while compiling these files: %s"
|
|
|
|
|
(length errors)
|
|
|
|
|
(ein:join-str " " (mapcar #'file-name-nondirectory it))))
|
|
|
|
|
(message "Compiled %s files" (length files))))
|
|
|
|
|
|
2012-08-12 17:48:24 +02:00
|
|
|
|
(defun ein:display-warning (message &optional level)
|
|
|
|
|
"Simple wrapper around `display-warning'.
|
|
|
|
|
LEVEL must be one of :emergency, :error or :warning (default).
|
|
|
|
|
This must be used only for notifying user.
|
|
|
|
|
Use `ein:log' for debugging and logging."
|
|
|
|
|
;; FIXME: Probably set BUFFER-NAME per notebook?
|
|
|
|
|
;; FIXME: Call `ein:log' here (but do not display in minibuffer).
|
|
|
|
|
(display-warning 'ein message level))
|
|
|
|
|
|
2012-08-27 14:11:41 +02:00
|
|
|
|
(defun ein:get-docstring (function)
|
|
|
|
|
"Return docstring of FUNCTION."
|
|
|
|
|
;; Borrowed from `ac-symbol-documentation'.
|
|
|
|
|
(with-temp-buffer
|
|
|
|
|
;; import help-xref-following
|
|
|
|
|
(require 'help-mode)
|
|
|
|
|
(erase-buffer)
|
|
|
|
|
(let ((standard-output (current-buffer))
|
|
|
|
|
(help-xref-following t)
|
|
|
|
|
(major-mode 'help-mode)) ; avoid error in Emacs 24
|
|
|
|
|
(describe-function-1 function))
|
|
|
|
|
(buffer-string)))
|
|
|
|
|
|
|
|
|
|
(defun ein:generate-menu (list-name-callback)
|
|
|
|
|
(mapcar (lambda (name-callback)
|
2012-08-27 14:22:27 +02:00
|
|
|
|
(destructuring-bind (name callback &rest args) name-callback
|
|
|
|
|
`[,name ,callback :help ,(ein:get-docstring callback) ,@args]))
|
2012-08-27 14:11:41 +02:00
|
|
|
|
list-name-callback))
|
|
|
|
|
|
2012-08-13 18:55:32 +02:00
|
|
|
|
|
|
|
|
|
;;; Generic getter
|
|
|
|
|
|
|
|
|
|
(defun ein:generic-getter (func-list)
|
2012-08-14 23:02:18 +02:00
|
|
|
|
"Internal function for generic getter functions (`ein:get-*').
|
2012-08-13 18:55:32 +02:00
|
|
|
|
|
|
|
|
|
FUNC-LIST is a list of function which takes no argument and
|
|
|
|
|
return what is desired or nil. Each function in FUNC-LIST is
|
|
|
|
|
called one by one and the first non-nil result will be used. The
|
|
|
|
|
function is not called when it is not bound. So, it is safe to
|
|
|
|
|
give functions defined in lazy-loaded sub-modules.
|
|
|
|
|
|
2012-08-14 23:02:18 +02:00
|
|
|
|
This is something similar to dispatching in generic function such
|
|
|
|
|
as `defgeneric' in EIEIO, but it takes no argument. Actual
|
|
|
|
|
implementation is chosen based on context (buffer, point, etc.).
|
|
|
|
|
This helps writing generic commands which requires same object
|
|
|
|
|
but can operate in different contexts."
|
2012-08-13 18:55:32 +02:00
|
|
|
|
(loop for func in func-list
|
|
|
|
|
if (and (functionp func) (funcall func))
|
|
|
|
|
return it))
|
|
|
|
|
|
|
|
|
|
(defun ein:get-url-or-port ()
|
|
|
|
|
(ein:generic-getter '(ein:get-url-or-port--notebooklist
|
|
|
|
|
ein:get-url-or-port--notebook
|
2012-08-20 00:58:45 +02:00
|
|
|
|
ein:get-url-or-port--worksheet
|
2012-08-13 18:55:32 +02:00
|
|
|
|
ein:get-url-or-port--shared-output
|
|
|
|
|
ein:get-url-or-port--connect)))
|
|
|
|
|
|
|
|
|
|
(defun ein:get-notebook ()
|
|
|
|
|
(ein:generic-getter '(ein:get-notebook--notebook
|
2012-08-20 00:58:45 +02:00
|
|
|
|
ein:get-notebook--worksheet
|
2012-08-13 18:55:32 +02:00
|
|
|
|
;; ein:get-notebook--shared-output
|
|
|
|
|
ein:get-notebook--connect)))
|
|
|
|
|
|
|
|
|
|
(defun ein:get-kernel ()
|
|
|
|
|
(ein:generic-getter '(ein:get-kernel--notebook
|
2012-08-20 00:58:45 +02:00
|
|
|
|
ein:get-kernel--worksheet
|
2012-08-13 18:55:32 +02:00
|
|
|
|
ein:get-kernel--shared-output
|
|
|
|
|
ein:get-kernel--connect)))
|
|
|
|
|
|
2012-08-15 23:04:20 +02:00
|
|
|
|
(defun ein:get-kernel-or-error ()
|
|
|
|
|
(or (ein:get-kernel)
|
|
|
|
|
(error "No kernel related to the current buffer.")))
|
|
|
|
|
|
2012-08-14 19:32:50 +02:00
|
|
|
|
(defun ein:get-cell-at-point ()
|
2012-08-20 02:55:49 +02:00
|
|
|
|
(ein:generic-getter '(ein:get-cell-at-point--worksheet
|
2012-08-14 19:32:50 +02:00
|
|
|
|
ein:get-cell-at-point--shared-output)))
|
|
|
|
|
|
2012-08-14 20:35:47 +02:00
|
|
|
|
(defun ein:get-traceback-data ()
|
2012-08-20 02:55:49 +02:00
|
|
|
|
(ein:generic-getter '(ein:get-traceback-data--worksheet
|
2012-08-14 20:35:47 +02:00
|
|
|
|
ein:get-traceback-data--shared-output
|
|
|
|
|
ein:get-traceback-data--connect)))
|
|
|
|
|
|
2012-08-03 22:30:20 +02:00
|
|
|
|
|
2012-08-12 17:33:35 +02:00
|
|
|
|
;;; File name translation (tramp support)
|
2012-08-03 22:30:20 +02:00
|
|
|
|
|
2012-08-04 00:26:30 +02:00
|
|
|
|
;; Probably it's better to define `ein:filename-translations-get' as
|
|
|
|
|
;; an EIEIO method so that I don't have to re-define functions such as
|
|
|
|
|
;; `ein:kernel-filename-to-python' and `ein:kernel-filename-from-python'.
|
|
|
|
|
|
2012-08-03 22:30:20 +02:00
|
|
|
|
(defun ein:filename-translations-get (url-or-port)
|
|
|
|
|
(ein:choose-setting 'ein:filename-translations url-or-port))
|
|
|
|
|
|
2012-08-04 00:26:30 +02:00
|
|
|
|
(defun ein:filename-to-python (url-or-port filename)
|
2012-08-03 22:30:20 +02:00
|
|
|
|
(ein:aif (car (ein:filename-translations-get url-or-port))
|
|
|
|
|
(funcall it filename)
|
|
|
|
|
filename))
|
|
|
|
|
|
2012-08-04 00:26:30 +02:00
|
|
|
|
(defun ein:filename-from-python (url-or-port filename)
|
2012-08-03 22:30:20 +02:00
|
|
|
|
(ein:aif (cadr (ein:filename-translations-get url-or-port))
|
|
|
|
|
(funcall it filename)
|
|
|
|
|
filename))
|
|
|
|
|
|
2012-08-03 23:52:34 +02:00
|
|
|
|
(defun ein:make-tramp-file-name (username remote-host python-filename)
|
|
|
|
|
"Old (with multi-hops) tramp compatability function.
|
|
|
|
|
Adapted from `slime-make-tramp-file-name'."
|
|
|
|
|
(if (boundp 'tramp-multi-methods)
|
|
|
|
|
(tramp-make-tramp-file-name nil nil
|
|
|
|
|
username
|
|
|
|
|
remote-host
|
|
|
|
|
python-filename)
|
|
|
|
|
(tramp-make-tramp-file-name nil
|
|
|
|
|
username
|
|
|
|
|
remote-host
|
|
|
|
|
python-filename)))
|
|
|
|
|
|
|
|
|
|
(defun ein:tramp-create-filename-translator (remote-host &optional username)
|
2012-08-03 23:06:46 +02:00
|
|
|
|
"Generate a pair of TO-PYTHON and FROM-PYTHON for
|
|
|
|
|
`ein:filename-translations'.
|
|
|
|
|
|
|
|
|
|
Usage::
|
|
|
|
|
|
|
|
|
|
(setq ein:filename-translations
|
|
|
|
|
`((8888
|
2012-08-03 23:52:34 +02:00
|
|
|
|
. ,(ein:tramp-create-filename-translator \"MY-HOSTNAME\"))))
|
2012-08-03 23:58:08 +02:00
|
|
|
|
;; Equivalently:
|
|
|
|
|
(setq ein:filename-translations
|
|
|
|
|
(lambda (url-or-port)
|
|
|
|
|
(when (equal url-or-port 8888)
|
|
|
|
|
(ein:tramp-create-filename-translator \"MY-HOSTNAME\"))))
|
2012-08-03 23:06:46 +02:00
|
|
|
|
|
|
|
|
|
This setting assumes that the IPython server which can be
|
|
|
|
|
connected using the port 8888 in localhost is actually running in
|
2012-08-03 23:52:34 +02:00
|
|
|
|
the host named MY-HOSTNAME.
|
|
|
|
|
|
|
|
|
|
Adapted from `slime-create-filename-translator'."
|
|
|
|
|
(require 'tramp)
|
|
|
|
|
(lexical-let ((remote-host remote-host)
|
|
|
|
|
(username (or username (user-login-name))))
|
|
|
|
|
(list (lambda (emacs-filename)
|
|
|
|
|
(tramp-file-name-localname
|
|
|
|
|
(tramp-dissect-file-name emacs-filename)))
|
|
|
|
|
(lambda (python-filename)
|
|
|
|
|
(ein:make-tramp-file-name username remote-host python-filename)))))
|
2012-08-03 23:06:46 +02:00
|
|
|
|
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
2012-06-11 19:30:45 +02:00
|
|
|
|
;;; utils.js compatible
|
2012-05-17 14:06:01 +02:00
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
(defun ein:utils-uuid ()
|
2012-05-12 22:22:23 +02:00
|
|
|
|
"Return string with random (version 4) UUID.
|
|
|
|
|
Adapted from org-mode's `org-id-uuid'."
|
|
|
|
|
(let ((rnd (md5 (format "%s%s%s%s%s%s%s"
|
|
|
|
|
(random t)
|
|
|
|
|
(current-time)
|
|
|
|
|
(user-uid)
|
|
|
|
|
(emacs-pid)
|
|
|
|
|
(user-full-name)
|
|
|
|
|
user-mail-address
|
|
|
|
|
(recent-keys)))))
|
|
|
|
|
(format "%s-%s-4%s-%s%s-%s"
|
|
|
|
|
(substring rnd 0 8)
|
|
|
|
|
(substring rnd 8 12)
|
|
|
|
|
(substring rnd 13 16)
|
|
|
|
|
(format "%x"
|
|
|
|
|
(logior
|
|
|
|
|
#b10000000
|
|
|
|
|
(logand
|
|
|
|
|
#b10111111
|
|
|
|
|
(string-to-number
|
|
|
|
|
(substring rnd 16 18) 16))))
|
|
|
|
|
(substring rnd 18 20)
|
|
|
|
|
(substring rnd 20 32))))
|
|
|
|
|
|
2012-05-07 14:41:15 +02:00
|
|
|
|
|
|
|
|
|
(provide 'ein-utils)
|
|
|
|
|
|
|
|
|
|
;;; ein-utils.el ends here
|