2019-10-07 16:27:38 -04:00
;;; ein-utils.el --- Utility module -*- lexical-binding: t -*-
2012-05-07 14:41:15 +02:00
;; 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:
2014-03-17 19:14:02 +01:00
( require 'cc-mode )
2012-05-07 14:41:15 +02:00
( require 'json )
2017-09-03 10:29:54 -05:00
( require 's )
2017-10-03 10:04:41 -05:00
( require 'dash )
2018-10-11 16:53:02 -04:00
( require 'url )
2018-10-15 16:57:22 -04:00
( require 'deferred )
2012-05-07 14:41:15 +02:00
2012-07-14 16:22:15 +02:00
;;; Macros and core functions/variables
2016-12-18 22:19:50 -06:00
( defmacro ein:with-undo-disabled ( &rest body )
2016-12-16 18:01:06 -06:00
" Temporarily disable undo recording while executing ` body `
while maintaining the undo list for the current buffer. "
` ( let ( ( buffer-undo-list t ) )
2016-12-18 22:19:50 -06:00
,@ body ) )
2016-12-16 18:01:06 -06:00
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*' . "
2012-10-15 18:34:49 +02:00
( declare ( debug ( ( &rest &or symbolp ( form ) ( gate symbolp &optional form ) )
body ) )
;; See: (info "(elisp) Specification List")
2012-08-14 20:02:29 +02:00
( 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
2019-03-29 12:11:12 -04:00
( defvar ein:local-variables ' ( )
" Modified by `ein:deflocal' " )
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 )
2019-03-29 12:11:12 -04:00
( put ',name 'permanent-local t )
( setq ein:local-variables ( append ein:local-variables ' ( , name ) ) ) ) )
2012-05-12 22:55:06 +02:00
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-08-29 21:32:06 +02:00
( defmacro ein:with-possibly-killed-buffer ( buffer &rest body )
" Execute BODY in BUFFER if BUFFER is live.
Execute BODY if BUFFER is not live anyway. "
( declare ( indent 1 ) ( debug t ) )
` ( if ( buffer-live-p , buffer )
( with-current-buffer , buffer
,@ body )
,@ 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 )
2019-10-06 09:27:47 -05:00
( modify-syntax-entry ?% " w " table )
2012-05-16 04:15:10 +02:00
table )
" Adapted from `python-dotty-syntax-table' . " )
2018-08-25 12:25:05 -04:00
( defun ein:beginning-of-object ( &optional code-syntax-table )
" Move to the beginning of the dotty.word.at.point. User may
specify a custom syntax table. If one is not supplied ` ein:dotty-syntax-table ' will
be assumed. "
( with-syntax-table ( or code-syntax-table ein:dotty-syntax-table )
( while ( re-search-backward " \\ ( \\ sw \\ | \\ s_ \\ | \\ s \\ . \\ | \\ s \\ \\ |[%@|] \\ ) \\ = "
( when ( > ( point ) 2000 ) ( - ( point ) 2000 ) )
t ) )
( re-search-forward " \\ =#[-+.<|] " nil t )
( when ( and ( looking-at " @ " ) )
( forward-char ) ) ) )
( defun ein:end-of-object ( &optional code-syntax-table )
" Move to the end of the dotty.word.at.point. User may specify a
custom syntax table. If one is not supplied
` ein:dotty-syntax-table ' will be assumed. "
( with-syntax-table ( or code-syntax-table ein:dotty-syntax-table )
( re-search-forward " \\ = \\ ( \\ sw \\ | \\ s_ \\ | \\ s \\ . \\ |#: \\ |[%|] \\ )* " ) ) )
( defun ein:object-start-pos ( )
" Return the starting position of the symbol under point.
The result is unspecified if there isn 't a symbol under the point. "
( save-excursion ( ein:beginning-of-object ) ( point ) ) )
( defun ein:object-end-pos ( )
( save-excursion ( ein:end-of-object ) ( point ) ) )
2019-10-05 15:12:20 -05:00
( defun ein:object-prefix-at-point ( )
" Similar to `ein:object-at-point' , but instead of returning the entire object
only returns the string up to the current point. For example, given pd.Series, if the
cursor is at the S then 'pd.S ' will be returned. "
( if ( ein:object-at-point )
( let* ( ( obj ( ein:object-at-point ) )
( delta ( - ( point ) ( ein:object-start-pos ) ) ) )
( substring obj 0 delta ) ) ) )
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
2018-06-05 14:57:53 -07:00
( ein:aif ( thing-at-point 'symbol )
2012-06-12 23:22:41 +02:00
it
( unless ( looking-at " ( " )
( search-backward " ( " ( point-at-bol ) t ) )
2018-06-05 14:57:53 -07:00
( thing-at-point 'symbol ) ) ) ) ) )
2012-05-16 05:04:30 +02:00
2018-09-18 21:48:07 -05:00
( defun ein:function-at-point ( )
" Similar to `ein:object-at-point' , but instead will looking for the function
at point, i.e. any word before then \" ( \", if it is present. "
( save-excursion
( unless ( looking-at " ( " )
( search-backward " ( " ( point-at-bol ) t ) )
( ein:object-at-point ) ) )
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 " ) ) )
2015-01-31 10:13:49 -06:00
( defun ein:flatten ( tree )
" Traverses the tree in order, collecting non-null leaves into a list. "
( let ( list )
( cl-labels ( ( traverse ( subtree )
( when subtree
( if ( consp subtree )
( progn
( traverse ( car subtree ) )
( traverse ( cdr subtree ) ) )
( push subtree list ) ) ) ) )
( traverse tree ) )
( nreverse list ) ) )
2012-05-13 02:51:47 +02:00
;;; URL utils
2012-12-17 17:14:26 +01:00
( defvar ein:url-localhost " 127.0.0.1 " )
2018-10-24 13:12:16 -04:00
( defsubst ein:glom-paths ( &rest paths )
2019-10-07 16:27:38 -04:00
( cl-loop with result = " "
for p in paths
if ( not ( zerop ( length p ) ) )
do ( setq result ( concat result ( ein:trim-left ( directory-file-name p ) " / " ) " / " ) )
end
finally return ( directory-file-name result ) ) )
2018-10-24 13:12:16 -04:00
2012-05-13 02:51:47 +02:00
( defun ein:url ( url-or-port &rest paths )
2019-10-07 16:27:38 -04:00
( when url-or-port
2019-01-15 11:26:09 -05:00
( if ( or ( integerp url-or-port )
2018-10-11 16:53:02 -04:00
( and ( stringp url-or-port ) ( string-match " ^[0-9]+$ " url-or-port ) ) )
( setq url-or-port ( format " http://localhost:%s " url-or-port ) ) )
( let ( ( parsed-url ( url-generic-parse-url url-or-port ) ) )
2018-10-24 13:12:16 -04:00
( when ( null ( url-host parsed-url ) )
( setq url-or-port ( concat " https:// " url-or-port ) )
( setq parsed-url ( url-generic-parse-url url-or-port ) ) )
2018-12-01 18:54:58 -05:00
( when ( or ( string= ( url-host parsed-url ) " localhost " )
2019-02-14 15:28:18 -05:00
( string= ( url-host parsed-url ) ein:url-localhost )
2018-12-01 18:54:58 -05:00
( string= ( url-host parsed-url ) " " ) )
2019-02-14 15:28:18 -05:00
( setf ( url-host parsed-url ) ein:url-localhost )
( setf ( url-type parsed-url ) " http " ) )
2018-10-24 13:12:16 -04:00
( directory-file-name ( concat ( file-name-as-directory ( url-recreate-url parsed-url ) )
( apply #' ein:glom-paths paths ) ) ) ) ) )
2012-05-13 02:51:47 +02:00
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-10-09 15:27:36 +02:00
;;; HTML utils
( defun ein:html-get-data-in-body-tag ( key )
" Very ad-hoc parser to get data in body tag. "
( ignore-errors
( save-excursion
( goto-char ( point-min ) )
( search-forward " <body " )
( search-forward-regexp ( format " %s= \\ ([^[:space:] \n ]+ \\ ) " key ) )
( match-string 1 ) ) ) )
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-28 12:18:12 +02:00
( defun ein:json-any-to-bool ( obj )
( if ( and obj ( not ( eq obj json-false ) ) ) t json-false ) )
2017-03-16 16:16:16 -05: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 127)) ; s/161/127/
;; (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)))
;; (defadvice json-encode (around encode-nil-as-json-empty-object activate)
;; (if (null object)
;; (setq ad-return-value "{}")
;; ad-do-it))
2016-01-03 08:23:55 -07:00
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 )
2019-01-21 13:37:27 -05:00
( ein:display-warning " Incompatible EWOC version.
2012-08-16 17:03:53 +02:00
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 " )
2016-03-01 16:02:00 -06:00
( error " Incompatible EWOC version. " ) ) ) )
2012-08-16 17:03:53 +02:00
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 )
2018-03-08 13:36:09 -06:00
( insert ( apply #' ein:propertize-read-only
( ein:maybe-truncate-string-lines string ein:truncate-long-cell-output )
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
2018-03-08 13:36:09 -06:00
( defun ein:maybe-truncate-string-lines ( string nlines )
" Truncate multi-line `string' to the number of lines specified by `nlines' . If actual
number of lines is less than ` nlines ' then just return the string. "
( if nlines
( let ( ( lines ( split-string string " [ \n ] " ) ) )
2018-05-25 11:43:39 -04:00
( if ( > ( length lines ) nlines )
2018-03-08 13:36:09 -06:00
( ein:join-str " \n " ( append ( butlast lines ( - ( length lines ) nlines ) )
( list " ... " ) ) )
string ) )
string ) )
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
2019-10-07 16:27:38 -04:00
( cl-loop for line in lines
for stripped = ( ein:trim-left line )
unless ( equal stripped " " )
collect ( - ( length line ) ( length stripped ) ) ) ) )
2018-05-27 12:04:13 -04:00
( if lens ( apply #' min lens ) 0 ) ) )
2012-05-23 13:24:21 +02:00
( trimmed
2019-10-07 16:27:38 -04:00
( cl-loop for line in lines
if ( > ( length line ) indent )
collect ( ein:trim-right ( substring line indent ) )
else
collect line ) ) )
2012-05-23 13:24:21 +02:00
( 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 )
2017-04-21 19:07:16 -05:00
( let ( ( keylist ( car clause ) )
( body ( cdr clause ) ) )
` ( , ( if ( listp keylist )
` ( or ,@ ( mapcar ( lambda ( key ) ` ( equal , str , key ) )
keylist ) )
't )
,@ body ) ) )
2012-05-07 14:41:15 +02:00
clauses ) ) )
2012-10-09 14:02:39 +02:00
;;; Text manipulation on buffer
( defun ein:find-leftmot-column ( beg end )
" Return the leftmost column in region BEG to END. "
( save-excursion
2012-10-09 14:23:06 +02:00
( let ( mincol )
( goto-char beg )
2012-10-09 14:02:39 +02:00
( while ( < ( point ) end )
( back-to-indentation )
( unless ( = ( point ) ( point-at-eol ) )
2012-10-09 14:23:06 +02:00
( setq mincol ( if mincol
( min mincol ( current-column ) )
( current-column ) ) ) )
2012-10-09 14:02:39 +02:00
( unless ( = ( forward-line 1 ) 0 )
2019-10-07 16:27:38 -04:00
( cl-return-from ein:find-leftmot-column mincol ) ) )
2012-10-09 14:02:39 +02:00
mincol ) ) )
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
2019-04-16 10:04:11 -05:00
( defun ein:completing-read ( &rest args )
" Wrap for emacs completing read functionality. Unless a more sophisticated completion framework has been installed (like helm or ivy), this function will default to using the slightly more sane ido completion framework. Arguments are the same as for `completing-read' . "
( if ( eq completing-read-function 'completing-read-default )
( apply #' ido-completing-read args )
( apply completing-read-function args ) ) )
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-12-06 17:41:39 +01:00
;; FIXME: this is not needed. See: `ein:plist-exclude'.
2019-10-07 16:27:38 -04:00
( cl-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-05-07 14:41:15 +02:00
2012-12-06 17:41:39 +01:00
( defun ein:plist-exclude ( plist keys )
" Exclude entries specified by KEYS in PLIST.
Example::
( ein:plist-exclude ' ( :a 1 :b 2 :c 3 :d 4 ) ' ( :b :c ) ) "
2019-10-07 16:27:38 -04:00
( cl-loop for ( k v ) on plist by 'cddr
unless ( memq k keys )
nconc ( list k v ) ) )
2012-12-06 17:41:39 +01:00
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)"
2019-10-07 16:27:38 -04:00
( cl-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 ) ) )
( cl-defun ein:list-insert-after ( list pivot new &key ( test #' eq ) )
2012-09-01 23:26:42 +02:00
" Insert NEW after PIVOT in LIST destructively.
Note: do not rely on that ` ein:list-insert-after ' change LIST in place.
Elements are compared using the function TEST ( default: ` eq ' ) . "
2019-10-07 16:27:38 -04:00
( cl-loop for rest on list
when ( funcall test ( car rest ) pivot )
return ( progn ( push new ( cdr rest ) ) list )
finally do ( error " PIVOT %S is not in LIST %S " pivot list ) ) )
2012-09-01 23:26:42 +02:00
2019-10-07 16:27:38 -04:00
( cl-defun ein:list-insert-before ( list pivot new &key ( test #' eq ) )
2012-09-01 23:26:42 +02:00
" Insert NEW before PIVOT in LIST destructively.
Note: do not rely on that ` ein:list-insert-before ' change LIST in place.
Elements are compared using the function TEST ( default: ` eq ' ) . "
( if ( and list ( funcall test ( car list ) pivot ) )
( cons new list )
2019-10-07 16:27:38 -04:00
( cl-loop for rest on list
when ( funcall test ( cadr rest ) pivot )
return ( progn ( push new ( cdr rest ) ) list )
finally do ( error " PIVOT %S is not in LIST %S " pivot list ) ) ) )
2012-09-01 23:26:42 +02:00
2019-10-07 16:27:38 -04:00
( cl-defun ein:list-move-left ( list elem &key ( test #' eq ) )
2012-09-02 03:30:55 +02:00
" Move ELEM in LIST left. TEST is used to compare elements "
2019-10-07 16:27:38 -04:00
( cl-macrolet ( ( == ( a b ) ` ( funcall test , a , b ) ) )
2012-09-02 03:30:55 +02:00
( cond
( ( == ( car list ) elem )
( append ( cdr list ) ( list ( car list ) ) ) )
( t
2019-10-07 16:27:38 -04:00
( cl-loop for rest on list
when ( == ( cadr rest ) elem )
return ( let ( ( prev ( car rest ) ) )
( setf ( car rest ) elem )
( setf ( cadr rest ) prev )
list )
finally do ( error " ELEM %S is not in LIST %S " elem list ) ) ) ) ) )
( cl-defun ein:list-move-right ( list elem &key ( test #' eq ) )
2012-09-02 04:05:44 +02:00
" Move ELEM in LIST right. TEST is used to compare elements "
2019-10-07 16:27:38 -04:00
( cl-loop with first = t
for rest on list
when ( funcall test ( car rest ) elem )
return ( if ( cdr rest )
( let ( ( next ( cadr rest ) ) )
( setf ( car rest ) next )
( setf ( cadr rest ) elem )
list )
( if first
list
( setcdr rest-1 nil )
( cons elem list ) ) )
finally do ( error " ELEM %S is not in LIST %S " elem list )
for rest-1 = rest
do ( setq first nil ) ) )
2012-09-02 04:05:44 +02:00
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 )
2018-05-28 14:19:26 -04:00
( ( boundp obj ) ( symbol-value obj ) )
2012-05-16 19:15:48 +02:00
( ( fboundp obj ) ( funcall obj ) ) ) )
2012-12-06 21:34:21 +01:00
( defun ein:choose-setting ( symbol value &optional single-p )
2012-05-14 21:03:23 +02:00
" Choose setting in stored in SYMBOL based on VALUE.
2012-12-06 21:34:21 +01:00
The value of SYMBOL can be string, alist or function.
SINGLE-P is a function which takes one argument. It must
return t when the value of SYMBOL can be used as a setting.
SINGLE-P is ` stringp ' by default. "
2018-05-28 14:19:26 -04:00
( let ( ( setting ( symbol-value symbol ) ) )
2012-05-14 21:03:23 +02:00
( cond
2012-12-06 21:34:21 +01:00
( ( funcall ( or single-p 'stringp ) setting ) setting )
2012-05-14 21:03:23 +02:00
( ( 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 )
2018-05-28 14:19:26 -04:00
( and ( boundp symbol ) ( symbol-value symbol ) ) )
2012-05-15 23:04:18 +02:00
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. "
2019-10-07 16:27:38 -04:00
( cl-loop for l in list
for i from 0
when ( not ( memq i indices ) )
collect l ) )
2012-05-07 14:41:15 +02:00
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. "
2019-10-07 16:27:38 -04:00
( let ( ( char-list ( cl-loop for i from 0 below ( length choices )
collect ( elt choices i ) ) )
2012-06-02 18:46:02 +02:00
( 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 ) )
2016-12-28 09:41:02 -06:00
( defun ein:wait-until ( predicate &optional predargs timeout-seconds )
2016-10-11 20:29:48 -05:00
" Wait until PREDICATE function returns non- `nil' .
PREDARGS is argument list for the PREDICATE function.
2016-12-28 09:41:02 -06:00
Make TIMEOUT-SECONDS larger \(default 5 ) to wait longer before timeout. "
2016-10-11 20:29:48 -05:00
( ein:log 'debug " WAIT-UNTIL start " )
2016-12-28 09:41:02 -06:00
( unless timeout-seconds ( setq timeout-seconds 5 ) )
2019-10-07 16:27:38 -04:00
( unless ( cl-loop repeat ( / timeout-seconds 0.05 )
when ( apply predicate predargs )
return t
;; borrowed from `deferred:sync!':
do ( sit-for 0.05 )
do ( sleep-for 0.05 ) )
2016-10-11 20:29:48 -05:00
( warn " Timeout " ) )
( ein:log 'debug " WAIT-UNTIL end " ) )
2018-05-25 11:43:39 -04:00
( defun ein:format-time-string ( format time )
" Apply format to time.
If ` format ' is a string, call ` format-time-string ',
otherwise it should be a function, which is called on ` time '. "
( cl-etypecase format
( string ( format-time-string format time ) )
( function ( funcall format time ) ) ) )
2012-08-12 17:33:35 +02:00
;;; Emacs utilities
2018-11-09 13:02:43 -05:00
( defmacro ein:message-whir ( mesg &rest body )
" Display MESG with a modest animation until ASYNC-CALL completes. "
2019-10-07 16:27:38 -04:00
` ( let* ( done-p
( done-callback ( lambda ( &rest _ignore ) ( setf done-p t ) ) )
( errback ( lambda ( &rest _ignore ) ( setf done-p 'error ) ) ) )
( ignore done-callback )
( ignore errback )
2018-11-09 13:02:43 -05:00
( ein:message-whir-subr , mesg ( lambda ( ) done-p ) )
,@ body ) )
2012-08-12 17:33:35 +02:00
2018-11-09 13:02:43 -05:00
( defun ein:message-whir-subr ( mesg doneback )
2019-01-15 11:26:09 -05:00
" Display MESG with a modest animation until done-p returns t.
2018-10-15 16:57:22 -04:00
DONEBACK returns t or 'error when calling process is done, and nil if not done. "
2019-10-07 16:27:38 -04:00
( let* ( ( mesg mesg )
( doneback doneback )
( count -1 ) )
( message " %s%s " mesg ( make-string ( 1+ ( % ( cl-incf count ) 3 ) ) ?. ) )
2018-10-24 13:12:16 -04:00
;; https://github.com/kiwanami/emacs-deferred/issues/28
;; "complicated timings of macro expansion lexical-let, deferred:lambda"
;; using deferred:loop instead
2018-10-15 16:57:22 -04:00
( deferred:$
2019-10-07 16:27:38 -04:00
( deferred:loop ( cl-loop for i from 1 below 30 by 1 collect i )
2018-10-24 13:12:16 -04:00
( lambda ( )
( deferred:$
( deferred:next
( lambda ( )
( ein:aif ( funcall doneback ) it
2019-10-07 16:27:38 -04:00
( message " %s%s " mesg ( make-string ( 1+ ( % ( cl-incf count ) 3 ) ) ?. ) )
2018-10-24 13:12:16 -04:00
( sleep-for 0 365 ) ) ) ) ) ) )
2018-10-15 16:57:22 -04:00
( deferred:nextc it
( lambda ( status )
2019-01-15 11:26:09 -05:00
( message " %s... %s " mesg
2018-10-24 13:12:16 -04:00
( if ( or ( null status ) ( eq status 'error ) ) " failed " " done " ) ) ) ) ) ) )
2018-10-15 16:57:22 -04:00
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-12-06 20:51:57 +01:00
( defvar ein:display-warning-once--db
( make-hash-table :test 'equal ) )
( defun ein:display-warning-once ( message &optional level )
" Call `ein:display-warning' once for same MESSAGE and LEVEL. "
( let ( ( key ( list message level ) ) )
( unless ( gethash key ein:display-warning-once--db )
( ein:display-warning message level )
( puthash key t ein:display-warning-once--db ) ) ) )
2018-12-12 09:37:21 -06:00
( defun ein:get-docstring ( function )
2012-08-27 14:11:41 +02:00
" Return docstring of FUNCTION. "
;; Borrowed from `ac-symbol-documentation'.
2018-12-12 09:37:21 -06:00
( 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 ) ) )
2012-08-27 14:11:41 +02:00
( defun ein:generate-menu ( list-name-callback )
( mapcar ( lambda ( name-callback )
2019-10-07 16:27:38 -04:00
( cl-destructuring-bind ( name callback &rest args ) name-callback
2012-08-27 14:22:27 +02:00
` [ , name , callback :help , ( ein:get-docstring callback ) ,@ args ] ) )
2012-08-27 14:11:41 +02:00
list-name-callback ) )
2019-01-14 12:56:32 -06:00
( defcustom ein:enable-gc-adjust t
" When t, EIN will set the `gc-cons-threshold' to an arbitrarily large value when opening notebookes. In some cases this adjustment will improve emacs performance, particularly when loading large notebooks. "
:type 'boolean
:group 'ein )
2019-10-07 16:27:38 -04:00
( let ( ( current-gc-cons-threshold gc-cons-threshold ) )
2018-03-08 09:03:09 -06:00
( defun ein:gc-prepare-operation ( )
2018-03-08 09:07:31 -06:00
( ein:log 'debug " [GC-PREPARE-OPERATION] Setting cons threshold to %s. " ( * current-gc-cons-threshold 10000 ) )
2019-01-14 12:56:32 -06:00
( when ein:enable-gc-adjust
( setq gc-cons-threshold ( * current-gc-cons-threshold 10000 ) ) ) )
2018-03-08 09:03:09 -06:00
( defun ein:gc-complete-operation ( )
2018-03-08 09:07:31 -06:00
( ein:log 'debug " [GC-COMPLETE-OPERATION] Reverting cons threshold to %s. " current-gc-cons-threshold )
2019-01-14 12:56:32 -06:00
( when ein:enable-gc-adjust
( setq gc-cons-threshold current-gc-cons-threshold ) ) ) )
2018-03-08 09:03:09 -06:00
2012-09-28 16:41:35 +02:00
;;; Git utilities
( defun ein:call-process ( command &optional args )
2012-09-28 17:00:59 +02:00
" Call COMMAND with ARGS and return its stdout as string or
` nil ' if COMMAND fails. It also checks if COMMAND executable
exists or not. "
2012-09-28 16:41:35 +02:00
( with-temp-buffer
( erase-buffer )
( and ( executable-find command )
( = ( apply #' call-process command nil t nil args ) 0 )
( buffer-string ) ) ) )
( defun ein:git-root-p ( &optional dir )
" Return `t' when DIR is root of git repository. "
( file-directory-p ( expand-file-name " .git " ( or dir default-directory ) ) ) )
( defun ein:git-dirty-p ( )
" Return `t' if the current directory is in git repository and it is dirty. "
( not ( equal ( ein:call-process
" git " ' ( " --no-pager " " status " " --porcelain " ) )
" " ) ) )
( defun ein:git-revision ( )
" Return abbreviated git revision if the current directory is in
git repository. "
( ein:call-process " git " ' ( " --no-pager " " log " " -n1 " " --format=format:%h " ) ) )
( defun ein:git-revision-dirty ( )
" Return `ein:git-revision' + \" -dirty \" suffix if the current
directory is in a dirty git repository. "
( ein:aand ( ein:git-revision )
( concat it ( if ( ein:git-dirty-p ) " -dirty " " " ) ) ) )
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 "
2016-03-01 16:02:00 -06:00
( random t )
( current-time )
( user-uid )
( emacs-pid )
( user-full-name )
user-mail-address
( recent-keys ) ) ) ) )
2012-05-12 22:22:23 +02:00
( format " %s-%s-4%s-%s%s-%s "
2016-03-01 16:02:00 -06:00
( 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-12 22:22:23 +02:00
2012-05-07 14:41:15 +02:00
( provide 'ein-utils )
;;; ein-utils.el ends here