Python autocompletion added, completly fast, and smart completion

This commit is contained in:
Sameer Rahmani 2013-04-02 03:44:19 +04:30
parent 742297e1ee
commit 35dfce4c79
13 changed files with 5573 additions and 17 deletions

38
README
View File

@ -1,14 +1,16 @@
Kuso IDE - A piece of Shit GNU Emacs based IDE
Copyright (C) 2010-2011 Sameer Rahmani <lxsameer@gnu.org>
Kuso IDE
========
A piece of Shit GNU Emacs based IDE
Copyright (C) 2010-2013 Sameer Rahmani <lxsameer@gnu.org>
Contents
==============================
--------
1. License
2. Dependencies
1. License
==============================
----------
Kuso IDE 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 2 of the License, or
@ -27,28 +29,32 @@ with this program; if not, write to the Free Software Foundation, Inc.,
under the term of GNU FDL.
2. Dependencies
=============================
Kuso IDE have these prequest softwares:
---------------
Kuso IDE required these softwares:
* GNU Emacs
Python dependencies
-------------------
* pep8
* pyflakes
* pychecker
* pylint
^^^^^^^^^^^^^^^^^^^
* pep8
* pyflakes
* pychecker
* pylint
For autocompetion:
* jedi
* epc
HTML dependencies
-----------------
* tidy
^^^^^^^^^^^^^^^^^
* tidy
CSS dependencies
----------------
* python-cssutils (pip install cssutils)
^^^^^^^^^^^^^^^^
* python-cssutils (pip install cssutils)
3. Installation
===============
---------------
Just run install.sh in its directory

View File

@ -19,6 +19,13 @@
(require 'python-mode)
(define-key py-mode-map (kbd "\C-c @") 'uncomment-region)
;; python autocomplete
(require 'deferred)
(require 'epc)
(autoload 'jedi:setup "jedi" nil t)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:setup-keys t)
;; php-mode -------------------------------------------------------------------
(autoload 'php-mode "php-mode" "PHP editing mode" t)
(add-to-list 'auto-mode-alist '("\\.php\\'" . php-mode))

View File

@ -19,6 +19,13 @@
(require 'python-mode)
(define-key py-mode-map (kbd "\C-c @") 'uncomment-region)
;; python autocomplete
(require 'deferred)
(require 'epc)
(autoload 'jedi:setup "jedi" nil t)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:setup-keys t)
;; php-mode -------------------------------------------------------------------
(autoload 'php-mode "php-mode" "PHP editing mode" t)
(add-to-list 'auto-mode-alist '("\\.php\\'" . php-mode))

504
conf/emacs.d/concurrent.el Normal file
View File

@ -0,0 +1,504 @@
;;; concurrent.el --- Concurrent utility functions for emacs lisp
;; Copyright (C) 2010, 2011, 2012 SAKURAI Masashi
;; Author: SAKURAI Masashi <m.sakurai at kiwanami.net>
;; Version: 0.3.1
;; Keywords: deferred, async, concurrent
;; Package-Requires: ((deferred "0.3.1"))
;; URL: https://github.com/kiwanami/emacs-deferred/blob/master/README-concurrent.markdown
;; This program 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.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; 'concurrent.el' is a higher level library for concurrent tasks
;; based on 'deferred.el'. This library has following features:
;;
;; - Generator
;; - Green thread
;; - Semaphore
;; - Dataflow
;; - Signal/Channel
(eval-when-compile
(require 'cl))
(require 'deferred)
(defvar cc:version nil "version number")
(setq cc:version "0.3")
;;; Code:
(defmacro cc:aif (test-form then-form &rest else-forms)
(declare (debug (form form &rest form)))
`(let ((it ,test-form))
(if it ,then-form ,@else-forms)))
(put 'cc:aif 'lisp-indent-function 2)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Generator
(defun cc:generator-replace-yield (tree)
"[internal] Replace `yield' symbols to calling a function in TREE."
(let (ret)
(loop for i in tree
do (cond
((eq i 'yield)
(push 'funcall ret)
(push i ret))
((listp i)
(push (cc:generator-replace-yield i) ret))
(t
(push i ret))))
(nreverse ret)))
(defun cc:generator-line (line)
"[internal] Return a macro expansion to execute the sexp LINE
asynchronously."
(cond
;; function object
((functionp line)
`(setq ,chain (deferred:nextc ,chain ,line)))
;; while loop form
((eq 'while (car line))
(let ((condition (cadr line))
(body (cddr line)))
`(setq ,chain
(deferred:nextc ,chain
(deferred:lambda (x)
(if ,condition
(deferred:nextc
(progn
,@(cc:generator-replace-yield body)) self)))))))
;; statement
(t
`(setq ,chain
(deferred:nextc ,chain
(deferred:lambda (x) ,(cc:generator-replace-yield line)))))))
(defmacro cc:generator (callback &rest body)
"Create a generator object. If BODY has `yield' symbols, it
means calling callback function CALLBACK."
(let ((chain (gensym))
(cc (gensym))
(waiter (gensym)))
`(lexical-let*
(,chain
(,cc ,callback)
(,waiter (deferred:new))
(yield (lambda (x) (funcall ,cc x) ,waiter)))
(setq ,chain ,waiter)
,@(loop for i in body
collect
(cc:generator-line i))
(lambda () (deferred:callback ,waiter)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Thread
(defun cc:thread-line (wait-time chain line)
"[internal] Return a macro expansion to execute the sexp LINE asynchronously.
WAIT-TIME is an interval time between tasks.
CHAIN is the previous deferred task."
(cond
;; function object
((functionp line)
`(setq ,chain (deferred:nextc ,chain ,line)))
;; while loop form
((eq 'while (car line))
(let ((condition (cadr line))
(body (cddr line))
(retsym (gensym)))
`(setq ,chain
(deferred:nextc ,chain
(deferred:lambda (x)
(if ,condition
(deferred:nextc
(let ((,retsym (progn ,@body)))
(if (deferred-p ,retsym) ,retsym
(deferred:wait ,wait-time)))
self)))))))
;; statement
(t
`(setq ,chain
(deferred:nextc ,chain
(lambda (x) ,line))))))
(defmacro cc:thread (wait-time-msec &rest body)
"Return a thread object."
(let ((chain (gensym))
(dstart (gensym)))
`(lexical-let*
(,chain
(,dstart (deferred:new)))
(setq ,chain ,dstart)
,@(loop for i in body
collect
(cc:thread-line wait-time-msec chain i))
(deferred:callback ,dstart))))
(put 'cc:thread 'lisp-indent-function 1)
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Semaphore
(defstruct cc:semaphore max-permits permits waiting-deferreds)
(defun cc:semaphore-create(permits-num)
"Return a semaphore object with PERMITS-NUM permissions."
(make-cc:semaphore :max-permits permits-num :permits permits-num))
(defun cc:semaphore-acquire(semaphore)
"Acquire an execution permission and return deferred object to chain.
If this semaphore object has permissions, the subsequent deferred
task is executed immediately. If this semaphore object has no
permissions, the subsequent deferred task is blocked. After the
permission is returned, the task is executed."
(cond
((< 0 (cc:semaphore-permits semaphore))
(decf (cc:semaphore-permits semaphore))
(deferred:succeed))
(t
(let ((d (deferred:new)))
(push d (cc:semaphore-waiting-deferreds semaphore))
d))))
(defun cc:semaphore-release(semaphore)
"Release an execution permission. The programmer is responsible to return the permissions."
(when (<= (cc:semaphore-max-permits semaphore)
(cc:semaphore-permits semaphore))
(error "Too many calling semaphore-release. [max:%s <= permits:%s]"
(cc:semaphore-max-permits semaphore)
(cc:semaphore-permits semaphore)))
(let ((waiting-deferreds
(cc:semaphore-waiting-deferreds semaphore)))
(cond
(waiting-deferreds
(let* ((d (car (last waiting-deferreds))))
(setf (cc:semaphore-waiting-deferreds semaphore)
(nbutlast waiting-deferreds))
(deferred:callback-post d)))
(t
(incf (cc:semaphore-permits semaphore)))))
semaphore)
(defun cc:semaphore-with (semaphore body-func &optional error-func)
"Execute the task BODY-FUNC asynchronously with the semaphore block."
(lexical-let ((semaphore semaphore))
(deferred:try
(deferred:nextc (cc:semaphore-acquire semaphore) body-func)
:catch
error-func
:finally
(lambda (x) (cc:semaphore-release semaphore)))))
(put 'cc:semaphore-with 'lisp-indent-function 1)
(defun cc:semaphore-release-all (semaphore)
"Release all permissions for resetting the semaphore object.
If the semaphore object has some blocked tasks, this function
return a list of the tasks and clear the list of the blocked
tasks in the semaphore object."
(setf (cc:semaphore-permits semaphore)
(cc:semaphore-max-permits semaphore))
(let ((ds (cc:semaphore-waiting-deferreds semaphore)))
(when ds
(setf (cc:semaphore-waiting-deferreds semaphore) nil))
ds))
(defun cc:semaphore-interrupt-all (semaphore)
"Clear the list of the blocked tasks in the semaphore and return a deferred object to chain.
This function is used for the interruption cases."
(when (cc:semaphore-waiting-deferreds semaphore)
(setf (cc:semaphore-waiting-deferreds semaphore) nil)
(setf (cc:semaphore-permits semaphore) 0))
(cc:semaphore-acquire semaphore))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Signal / Channel
(defun cc:signal-channel (&optional name parent-channel)
"Create a channel.
NAME is a channel name for debug.
PARENT-CHANNEL is an upstream channel. The observers of this channel can receive the upstream signals.
In the case of using the function `cc:signal-send', the observers of the upstream channel can not receive the signals of this channel. The function `cc:signal-send-global' can send a signal to the upstream channels from the downstream channels."
(lexical-let
((ch (cons
(or name (format "signal%s" (deferred:uid))) ; name for debug
(cons
parent-channel ; parent-channel
nil)))) ; observers
(when parent-channel
(cc:signal-connect
parent-channel
t (lambda (event)
(destructuring-bind
(event-name event-args) event
(apply 'cc:signal-send
ch event-name event-args)))))
ch))
(defmacro cc:signal-name (ch)
"[internal] Return signal name."
`(car ,ch))
(defmacro cc:signal-parent-channel (ch)
"[internal] Return parent channel object."
`(cadr ,ch))
(defmacro cc:signal-observers (ch)
"[internal] Return observers."
`(cddr ,ch))
(defun cc:signal-connect (channel event-sym &optional callback)
"Append an observer for EVENT-SYM of CHANNEL and return a deferred object.
If EVENT-SYM is `t', the observer receives all signals of the channel.
If CALLBACK function is given, the deferred object executes the
CALLBACK function asynchronously. One can connect subsequent
tasks to the returned deferred object."
(let ((d (if callback
(deferred:new callback)
(deferred:new))))
(push (cons event-sym d)
(cc:signal-observers channel))
d))
(defun cc:signal-send (channel event-sym &rest args)
"Send a signal to CHANNEL. If ARGS values are given, observers can get the values by following code: (lambda (event) (destructuring-bind (event-sym (args)) event ... )). "
(let ((observers (cc:signal-observers channel))
(event (list event-sym args)))
(loop for i in observers
for name = (car i)
for d = (cdr i)
if (or (eq event-sym name) (eq t name))
do (deferred:callback-post d event))))
(defun cc:signal-send-global (channel event-sym &rest args)
"Send a signal to the most upstream channel. "
(cc:aif (cc:signal-parent-channel channel)
(apply 'cc:signal-send-global it event-sym args)
(apply 'cc:signal-send channel event-sym args)))
(defun cc:signal-disconnect (channel deferred)
"Remove the observer object DEFERRED from CHANNEL and return
the removed deferred object. "
(let ((observers (cc:signal-observers channel)) deleted)
(setf
(cc:signal-observers channel) ; place
(loop for i in observers
for d = (cdr i)
unless (eq d deferred)
collect i
else
do (push i deleted)))
deleted))
(defun cc:signal-disconnect-all (channel)
"Remove all observers."
(setf
(cc:signal-observers channel) ; place
nil))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Dataflow
;; Dataflow variable entry
(defstruct cc:dataflow key (value 'cc:dataflow-undefine) deferred-list)
(defun cc:dataflow-undefine-p (obj)
"[internal] If the variable entry is not bound, return `t'."
(eq 'cc:dataflow-undefine (cc:dataflow-value obj)))
(defun cc:dataflow-environment (&optional parent-env test-func channel)
"Create a dataflow environment.
PARENT-ENV is the default environment. If this environment doesn't have the entry A and the parent one has the entry A, this environment can return the entry A. One can override the entry, setting another entry A to this environment.
TEST-FUNC is a test function that compares the entry keys. The default function is `equal'.
CHANNEL is a channel object that sends signals of variable events. Observers can receive following signals:
-get-first : the fist referrer is waiting for binding,
-get-waiting : another referrer is waiting for binding,
-set : a value is bound,
-get : returned a bound value,
-clear : cleared one entry,
-clear-all : cleared all entries.
"
(let ((this (list parent-env
(or test-func 'equal)
(or channel
(cc:signal-channel
'dataflow
(and parent-env
(cc:dataflow-channel parent-env)))))))
(cc:dataflow-init-connect this)
this))
(defun cc:dataflow-init-connect (df)
"[internal] Initialize the channel object."
(lexical-let ((df df))
(cc:dataflow-connect
df 'set
(lambda (args)
(destructuring-bind (event (key)) args
(let* ((obj (cc:dataflow-get-object-for-value df key))
(value (and obj (cc:dataflow-value obj))))
(when obj
(loop for i in (cc:aif (cc:dataflow-get-object-for-deferreds df key)
(cc:dataflow-deferred-list it) nil)
do (deferred:callback-post i value))
(setf (cc:dataflow-deferred-list obj) nil))))))))
(defmacro cc:dataflow-parent-environment (df)
"[internal] Return the parent environment."
`(car ,df))
(defmacro cc:dataflow-test (df)
"[internal] Return the test function."
`(cadr ,df))
(defmacro cc:dataflow-channel (df)
"[internal] Return the channel object."
`(caddr ,df))
(defmacro cc:dataflow-list (df)
"[internal] Return the list of deferred object which are waiting for value binding."
`(cdddr ,df))
(defun cc:dataflow-get-object-for-value (df key)
"[internal] Return an entry object that is indicated by KEY.
If the environment DF doesn't have the entry and the parent one has the entry, this function returns the entry of the parent environment. This function doesn't affect the waiting list."
(or
(loop for i in (cc:dataflow-list df)
with test = (cc:dataflow-test df)
if (and (funcall test key (cc:dataflow-key i))
(not (cc:dataflow-undefine-p i)))
return i)
(deferred:aand
(cc:dataflow-parent-environment df)
(cc:dataflow-get-object-for-value it key))))
(defun cc:dataflow-get-object-for-deferreds (df key)
"[internal] Return a list of the deferred objects those are waiting for value binding.
This function doesn't affect the waiting list and doesn't refer the parent environment."
(loop for i in (cc:dataflow-list df)
with test = (cc:dataflow-test df)
if (funcall test key (cc:dataflow-key i))
return i))
(defun cc:dataflow-connect (df event-sym &optional callback)
"Append an observer for EVENT-SYM of the channel of DF and return a deferred object.
See the docstring of `cc:dataflow-environment' for details."
(cc:signal-connect (cc:dataflow-channel df) event-sym callback))
(defun cc:dataflow-signal (df event &optional arg)
"[internal] Send a signal to the channel of DF."
(cc:signal-send (cc:dataflow-channel df) event arg))
(defun cc:dataflow-get (df key)
"Return a deferred object that can refer the value which is indicated by KEY.
If DF has the entry that bound value, the subsequent deferred task is executed immediately.
If not, the task is deferred till a value is bound."
(let ((obj (cc:dataflow-get-object-for-value df key)))
(cond
((and obj (cc:dataflow-value obj))
(cc:dataflow-signal df 'get key)
(deferred:succeed (cc:dataflow-value obj)))
(t
(setq obj (cc:dataflow-get-object-for-deferreds df key))
(unless obj
(setq obj (make-cc:dataflow :key key))
(push obj (cc:dataflow-list df))
(cc:dataflow-signal df 'get-first key))
(let ((d (deferred:new)))
(push d (cc:dataflow-deferred-list obj))
(cc:dataflow-signal df 'get-waiting key)
d)))))
(defun cc:dataflow-get-sync (df key)
"Return the value which is indicated by KEY synchronously.
If the environment DF doesn't have an entry of KEY, this function returns nil."
(let ((obj (cc:dataflow-get-object-for-value df key)))
(and obj (cc:dataflow-value obj))))
(defun cc:dataflow-set (df key value)
"Bind the VALUE to KEY in the environment DF.
If DF already has the bound entry of KEY, this function throws an error signal.
VALUE can be nil as a value."
(let ((obj (cc:dataflow-get-object-for-deferreds df key)))
(cond
((and obj (not (cc:dataflow-undefine-p obj)))
;; overwrite!
(error "Can not set a dataflow value. The key [%s] has already had a value. NEW:[%s] OLD:[%s]" key value (cc:dataflow-value obj)))
(obj
(setf (cc:dataflow-value obj) value))
(t
;; just value arrived
(push (make-cc:dataflow :key key :value value)
(cc:dataflow-list df))))
;; value arrived and start deferred objects
(cc:dataflow-signal df 'set key)
value))
(defun cc:dataflow-clear (df key)
"Clear the entry which is indicated by KEY.
This function does nothing for the waiting deferred objects."
(cc:dataflow-signal df 'clear key)
(setf (cc:dataflow-list df)
(loop for i in (cc:dataflow-list df)
with test = (cc:dataflow-test df)
unless (funcall test key (cc:dataflow-key i))
collect i)))
(defun cc:dataflow-get-avalable-pairs (df)
"Return an available key-value alist in the environment DF and the parent ones."
(append
(loop for i in (cc:dataflow-list df)
for key = (cc:dataflow-key i)
for val = (cc:dataflow-value i)
unless (cc:dataflow-undefine-p i) collect (cons key val))
(deferred:aand
(cc:dataflow-parent-environment df)
(cc:dataflow-get-avalable-pairs it))))
(defun cc:dataflow-get-waiting-keys (df)
"Return a list of keys which have waiting deferred objects in the environment DF and the parent ones."
(append
(loop for i in (cc:dataflow-list df)
for key = (cc:dataflow-key i)
for val = (cc:dataflow-value i)
if (cc:dataflow-undefine-p i) collect key)
(deferred:aand
(cc:dataflow-parent-environment df)
(cc:dataflow-get-waiting-keys it))))
(defun cc:dataflow-clear-all (df)
"Clear all entries in the environment DF.
This function does nothing for the waiting deferred objects."
(cc:dataflow-signal df 'clear-all)
(setf (cc:dataflow-list df) nil))
(provide 'concurrent)
;;; concurrent.el ends here

1588
conf/emacs.d/ctable.el Normal file

File diff suppressed because it is too large Load Diff

952
conf/emacs.d/deferred.el Normal file
View File

@ -0,0 +1,952 @@
;;; deferred.el --- Simple asynchronous functions for emacs lisp
;; Copyright (C) 2010, 2011, 2012 SAKURAI Masashi
;; Author: SAKURAI Masashi <m.sakurai at kiwanami.net>
;; Version: 0.3.1
;; Keywords: deferred, async
;; URL: https://github.com/kiwanami/emacs-deferred
;; This program 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.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; 'deferred.el' is a simple library for asynchronous tasks.
;; [https://github.com/kiwanami/emacs-deferred]
;; The API is almost the same as JSDeferred written by cho45. See the
;; JSDeferred and Mochikit.Async web sites for further documentations.
;; [https://github.com/cho45/jsdeferred]
;; [http://mochikit.com/doc/html/MochiKit/Async.html]
;; A good introduction document (JavaScript)
;; [http://cho45.stfuawsc.com/jsdeferred/doc/intro.en.html]
;;; Samples:
;; ** HTTP Access
;; (require 'url)
;; (deferred:$
;; (deferred:url-retrieve "http://www.gnu.org")
;; (deferred:nextc it
;; (lambda (buf)
;; (insert (with-current-buffer buf (buffer-string)))
;; (kill-buffer buf))))
;; ** Invoking command tasks
;; (deferred:$
;; (deferred:process "wget" "-O" "a.jpg" "http://www.gnu.org/software/emacs/tour/images/splash.png")
;; (deferred:nextc it
;; (lambda (x) (deferred:process "convert" "a.jpg" "-resize" "100x100" "jpg:b.jpg")))
;; (deferred:nextc it
;; (lambda (x)
;; (insert-image (create-image (expand-file-name "b.jpg") 'jpeg nil)))))
;; See the readme for further API documentation.
;; ** Applications
;; *Inertial scrolling for Emacs
;; [https://github.com/kiwanami/emacs-inertial-scroll]
;; This program makes simple multi-thread function, using
;; deferred.el.
(eval-when-compile
(require 'cl))
(defvar deferred:version nil "deferred.el version")
(setq deferred:version "0.3")
;;; Code:
(defmacro deferred:aand (test &rest rest)
"[internal] Anaphoric AND."
(declare (debug ("test" form &rest form)))
`(let ((it ,test))
(if it ,(if rest `(deferred:aand ,@rest) 'it))))
(defmacro deferred:$ (&rest elements)
"Anaphoric function chain macro for deferred chains."
(declare (debug (&rest form)))
`(let (it)
,@(loop for i in elements
with it = nil
collect
`(setq it ,i))
it))
(defmacro deferred:lambda (args &rest body)
"Anaphoric lambda macro for self recursion."
(declare (debug ("args" form &rest form)))
(let ((argsyms (loop for i in args collect (gensym))))
`(lambda (,@argsyms)
(lexical-let (self)
(setq self (lambda( ,@args ) ,@body))
(funcall self ,@argsyms)))))
(defun deferred:setTimeout (f msec)
"[internal] Timer function that emulates the `setTimeout' function in JS."
(run-at-time (/ msec 1000.0) nil f))
(defun deferred:cancelTimeout (id)
"[internal] Timer cancellation function that emulates the `cancelTimeout' function in JS."
(cancel-timer id))
(defun deferred:run-with-idle-timer (sec f)
"[internal] Wrapper function for run-with-idle-timer."
(run-with-idle-timer sec nil f))
(defun deferred:call-lambda (f &optional arg)
"[internal] Call a function with one or zero argument safely.
The lambda function can define with zero and one argument."
(condition-case err
(funcall f arg)
('wrong-number-of-arguments
(display-warning 'deferred "\
Callback that takes no argument may be specified.
Passing callback with no argument is deprecated.
Callback must take one argument.
Or, this error is coming from somewhere inside of the callback: %S" err)
(condition-case err2
(funcall f)
('wrong-number-of-arguments
(signal 'wrong-number-of-arguments (cdr err))))))) ; return the first error
;; debug
(eval-and-compile
(defvar deferred:debug nil "Debug output switch."))
(defvar deferred:debug-count 0 "[internal] Debug output counter.")
(defmacro deferred:message (&rest args)
"[internal] Debug log function."
(when deferred:debug
`(progn
(with-current-buffer (get-buffer-create "*deferred:debug*")
(save-excursion
(goto-char (point-max))
(insert (format "%5i %s\n" deferred:debug-count (format ,@args)))))
(incf deferred:debug-count))))
(defun deferred:message-mark ()
"[internal] Debug log function."
(interactive)
(deferred:message "==================== mark ==== %s"
(format-time-string "%H:%M:%S" (current-time))))
(defun deferred:pp (d)
(require 'pp)
(deferred:$
(deferred:nextc d
(lambda (x)
(pp-display-expression x "*deferred:pp*")))
(deferred:error it
(lambda (e)
(pp-display-expression e "*deferred:pp*")))
(deferred:nextc it
(lambda (x) (pop-to-buffer "*deferred:pp*")))))
(defvar deferred:debug-on-signal nil
"If non nil, the value `debug-on-signal' is substituted this
value in the `condition-case' form in deferred
implementations. Then, Emacs debugger can catch an error occurred
in the asynchronous tasks.")
(defmacro deferred:condition-case (var protected-form &rest handlers)
"[internal] Custom condition-case. See the comment for
`deferred:debug-on-signal'."
(declare (debug (symbolp form &rest form)))
`(cond
((null deferred:debug-on-signal)
(condition-case ,var ,protected-form ,@handlers))
(t
(let ((deferred:debug-on-signal-backup debug-on-signal))
(setq debug-on-signal deferred:debug-on-signal)
(unwind-protect
(condition-case ,var ,protected-form ,@handlers)
(setq debug-on-signal deferred:debug-on-signal-backup))))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Back end functions of deferred tasks
(defvar deferred:tick-time 0.001
"Waiting time between asynchronous tasks (second).
The shorter waiting time increases the load of Emacs. The end
user can tune this paramter. However, applications should not
modify it because the applications run on various environments.")
(defvar deferred:queue nil
"[internal] The execution queue of deferred objects.
See the functions `deferred:post-task' and `deferred:worker'.")
(defmacro deferred:pack (a b c)
`(cons ,a (cons ,b ,c)))
(defun deferred:schedule-worker ()
"[internal] Schedule consuming a deferred task in the execution queue."
(run-at-time deferred:tick-time nil 'deferred:worker))
(defun deferred:post-task (d which &optional arg)
"[internal] Add a deferred object to the execution queue
`deferred:queue' and schedule to execute.
D is a deferred object. WHICH is a symbol, `ok' or `ng'. ARG is
an argument value for execution of the deferred task."
(push (deferred:pack d which arg) deferred:queue)
(deferred:message "QUEUE-POST [%s]: %s"
(length deferred:queue) (deferred:pack d which arg))
(deferred:schedule-worker)
d)
(defun deferred:clear-queue ()
"Clear the execution queue. For test and debugging."
(interactive)
(deferred:message "QUEUE-CLEAR [%s -> 0]" (length deferred:queue))
(setq deferred:queue nil))
(defun deferred:esc-msg (msg)
"[internal] Escaping the character '%'."
(replace-regexp-in-string
"\\([^%]\\|^\\)%\\([^%]\\)" "\\1%%\\2" msg))
(defun deferred:worker ()
"[internal] Consume a deferred task.
Mainly this function is called by timer asynchronously."
(when deferred:queue
(let* ((pack (car (last deferred:queue)))
(d (car pack))
(which (cadr pack))
(arg (cddr pack)) value)
(setq deferred:queue (nbutlast deferred:queue))
(condition-case err
(setq value (deferred:exec-task d which arg))
(error
(deferred:message "ERROR : %s" err)
(message "deferred error : %s" err)))
value)))
(defun deferred:flush-queue! ()
"Call all deferred tasks synchronously. For test and debugging."
(let (value)
(while deferred:queue
(setq value (deferred:worker)))
value))
(defun deferred:sync! (d)
"Wait for the given deferred task. For test and debugging."
(progn
(lexical-let ((last-value 'deferred:undefined*))
(deferred:nextc d
(lambda (x) (setq last-value x)))
(while (eq 'deferred:undefined* last-value)
(sit-for 0.05)
(sleep-for 0.05))
last-value)))
;; Struct: deferred
;;
;; callback : a callback function (default `deferred:default-callback')
;; errorback : an errorback function (default `deferred:default-errorback')
;; cancel : a canceling function (default `deferred:default-cancel')
;; next : a next chained deferred object (default nil)
;; status : if 'ok or 'ng, this deferred has a result (error) value. (default nil)
;; value : saved value (default nil)
;;
(defstruct deferred
(callback 'deferred:default-callback)
(errorback 'deferred:default-errorback)
(cancel 'deferred:default-cancel)
next status value)
(defun deferred:default-callback (i)
"[internal] Default callback function."
(identity i))
(defun deferred:default-errorback (error-msg)
"[internal] Default errorback function."
(error (deferred:esc-msg
(cond
((stringp error-msg) error-msg)
((listp error-msg) (cadr error-msg))
(t (format "%S" error-msg))))))
(defun deferred:default-cancel (d)
"[internal] Default canceling function."
(deferred:message "CANCEL : %s" d)
(setf (deferred-callback d) 'deferred:default-callback)
(setf (deferred-errorback d) 'deferred:default-errorback)
(setf (deferred-next d) nil)
d)
(defun deferred:exec-task (d which &optional arg)
"[internal] Executing deferred task. If the deferred object has
next deferred task or the return value is a deferred object, this
function adds the task to the execution queue.
D is a deferred object. WHICH is a symbol, `ok' or `ng'. ARG is
an argument value for execution of the deferred task."
(deferred:message "EXEC : %s / %s / %s" d which arg)
(when (null d) (error "deferred:exec-task was given a nil."))
(let ((callback (if (eq which 'ok)
(deferred-callback d)
(deferred-errorback d))))
(cond
(callback
(let (value (next-deferred (deferred-next d)))
(deferred:condition-case err
(progn
(setq value
(deferred:call-lambda callback arg))
(cond
((deferred-p value)
(deferred:message "WAIT NEST : %s" value)
(if next-deferred
(deferred:set-next value next-deferred)
value))
(t
(if next-deferred
(deferred:post-task next-deferred 'ok value)
(setf (deferred-status d) 'ok)
(setf (deferred-value d) value)
value))))
(error
(cond
(next-deferred
(deferred:post-task next-deferred 'ng (error-message-string err)))
(deferred:onerror
(deferred:call-lambda deferred:onerror err))
(t
(deferred:message "ERROR : %s" err)
(message "deferred error : %s" err)
(setf (deferred-status d) 'ng)
(setf (deferred-value d) (error-message-string err))
(deferred:esc-msg (error-message-string err))))))))
(t ; <= (null callback)
(let ((next-deferred (deferred-next d)))
(cond
(next-deferred
(deferred:exec-task next-deferred which arg))
((eq which 'ok) arg)
(t (error (deferred:esc-msg arg)))))))))
(defun deferred:set-next (prev next)
"[internal] Connect deferred objects."
(setf (deferred-next prev) next)
(cond
((eq 'ok (deferred-status prev))
(setf (deferred-status prev) nil)
(let ((ret (deferred:exec-task
next 'ok (deferred-value prev))))
(if (deferred-p ret) ret
next)))
((eq 'ng (deferred-status prev))
(setf (deferred-status prev) nil)
(let ((ret (deferred:exec-task next 'ng (deferred-value prev))))
(if (deferred-p ret) ret
next)))
(t
next)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Basic functions for deferred objects
(defun deferred:new (&optional callback)
"Create a deferred object."
(if callback
(make-deferred :callback callback)
(make-deferred)))
(defun deferred:callback (d &optional arg)
"Start deferred chain with a callback message."
(deferred:exec-task d 'ok arg))
(defun deferred:errorback (d &optional arg)
"Start deferred chain with an errorback message."
(deferred:exec-task d 'ng arg))
(defun deferred:callback-post (d &optional arg)
"Add the deferred object to the execution queue."
(deferred:post-task d 'ok arg))
(defun deferred:errorback-post (d &optional arg)
"Add the deferred object to the execution queue."
(deferred:post-task d 'ng arg))
(defun deferred:cancel (d)
"Cancel all callbacks and deferred chain in the deferred object."
(deferred:message "CANCEL : %s" d)
(funcall (deferred-cancel d) d)
d)
(defun deferred:status (d)
"Return a current status of the deferred object. The returned value means following:
`ok': the callback was called and waiting for next deferred.
`ng': the errorback was called and waiting for next deferred.
nil: The neither callback nor errorback was not called."
(deferred-status d))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Basic utility functions
(defvar deferred:onerror nil
"Default error handler. This value is nil or a function that
have one argument for the error message.")
(defun deferred:succeed (&optional arg)
"Create a synchronous deferred object."
(let ((d (deferred:new)))
(deferred:exec-task d 'ok arg)
d))
(defun deferred:fail (&optional arg)
"Create a synchronous deferred object."
(let ((d (deferred:new)))
(deferred:exec-task d 'ok arg)
d))
(defun deferred:next (&optional callback arg)
"Create a deferred object and schedule executing. This function
is a short cut of following code:
(deferred:callback-post (deferred:new callback))."
(let ((d (if callback
(make-deferred :callback callback)
(make-deferred))))
(deferred:callback-post d arg)
d))
(defun deferred:nextc (d callback)
"Create a deferred object with OK callback and connect it to the given deferred object."
(let ((nd (make-deferred :callback callback)))
(deferred:set-next d nd)))
(defun deferred:error (d callback)
"Create a deferred object with errorback and connect it to the given deferred object."
(let ((nd (make-deferred :errorback callback)))
(deferred:set-next d nd)))
(defun deferred:watch (d callback)
"Create a deferred object with watch task and connect it to the given deferred object.
The watch task CALLBACK can not affect deferred chains with
return values. This function is used in following purposes,
simulation of try-finally block in asynchronous tasks, progress
monitoring of tasks."
(lexical-let*
((callback callback)
(normal (lambda (x) (ignore-errors (deferred:call-lambda callback x)) x))
(err (lambda (e) (ignore-errors (deferred:call-lambda callback e)) (error e))))
(let ((nd (make-deferred :callback normal :errorback err)))
(deferred:set-next d nd))))
(defun deferred:wait (msec)
"Return a deferred object scheduled at MSEC millisecond later."
(lexical-let
((d (deferred:new)) (start-time (float-time)) timer)
(deferred:message "WAIT : %s" msec)
(setq timer (deferred:setTimeout
(lambda ()
(deferred:exec-task d 'ok
(* 1000.0 (- (float-time) start-time)))
nil) msec))
(setf (deferred-cancel d)
(lambda (x)
(deferred:cancelTimeout timer)
(deferred:default-cancel x)))
d))
(defun deferred:wait-idle (msec)
"Return a deferred object which will run when Emacs has been
idle for MSEC millisecond."
(lexical-let
((d (deferred:new)) (start-time (float-time)) timer)
(deferred:message "WAIT-IDLE : %s" msec)
(setq timer
(deferred:run-with-idle-timer
(/ msec 1000.0)
(lambda ()
(deferred:exec-task d 'ok
(* 1000.0 (- (float-time) start-time)))
nil)))
(setf (deferred-cancel d)
(lambda (x)
(deferred:cancelTimeout timer)
(deferred:default-cancel x)))
d))
(defun deferred:call (f &rest args)
"Call the given function asynchronously."
(lexical-let ((f f) (args args))
(deferred:next
(lambda (x)
(apply f args)))))
(defun deferred:apply (f &optional args)
"Call the given function asynchronously."
(lexical-let ((f f) (args args))
(deferred:next
(lambda (x)
(apply f args)))))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Utility functions
(defun deferred:empty-p (times-or-list)
"[internal] Return non-nil if TIMES-OR-LIST is the number zero or nil."
(or (and (numberp times-or-list) (<= times-or-list 0))
(and (listp times-or-list) (null times-or-list))))
(defun deferred:loop (times-or-list func)
"Return a iteration deferred object."
(deferred:message "LOOP : %s" times-or-list)
(if (deferred:empty-p times-or-list) (deferred:next)
(lexical-let*
(items (rd
(cond
((numberp times-or-list)
(loop for i from 0 below times-or-list
with ld = (deferred:next)
do
(push ld items)
(setq ld
(lexical-let ((i i) (func func))
(deferred:nextc ld (lambda (x) (deferred:call-lambda func i)))))
finally return ld))
((listp times-or-list)
(loop for i in times-or-list
with ld = (deferred:next)
do
(push ld items)
(setq ld
(lexical-let ((i i) (func func))
(deferred:nextc ld (lambda (x) (deferred:call-lambda func i)))))
finally return ld)))))
(setf (deferred-cancel rd)
(lambda (x) (deferred:default-cancel x)
(loop for i in items
do (deferred:cancel i))))
rd)))
(defun deferred:trans-multi-args (args self-func list-func main-func)
"[internal] Check the argument values and dispatch to methods."
(cond
((and (= 1 (length args)) (consp (car args)) (not (functionp (car args))))
(let ((lst (car args)))
(cond
((or (null lst) (null (car lst)))
(deferred:next))
((deferred:aand lst (car it) (or (functionp it) (deferred-p it)))
;; a list of deferred objects
(funcall list-func lst))
((deferred:aand lst (consp it))
;; an alist of deferred objects
(funcall main-func lst))
(t (error "Wrong argument type. %s" args)))))
(t (funcall self-func args))))
(defun deferred:parallel-array-to-alist (lst)
"[internal] Translation array to alist."
(loop for d in lst
for i from 0 below (length lst)
collect (cons i d)))
(defun deferred:parallel-alist-to-array (alst)
"[internal] Translation alist to array."
(loop for pair in
(sort alst (lambda (x y)
(< (car x) (car y))))
collect (cdr pair)))
(defun deferred:parallel-func-to-deferred (alst)
"[internal] Normalization for parallel and earlier arguments."
(loop for pair in alst
for d = (cdr pair)
collect
(progn
(unless (deferred-p d)
(setf (cdr pair) (deferred:next d)))
pair)))
(defun deferred:parallel-main (alst)
"[internal] Deferred alist implementation for `deferred:parallel'. "
(deferred:message "PARALLEL<KEY . VALUE>" )
(lexical-let ((nd (deferred:new))
(len (length alst))
values)
(loop for pair in
(deferred:parallel-func-to-deferred alst)
with cd ; current child deferred
do
(lexical-let ((name (car pair)))
(setq cd
(deferred:nextc (cdr pair)
(lambda (x)
(push (cons name x) values)
(deferred:message "PARALLEL VALUE [%s/%s] %s"
(length values) len (cons name x))
(when (= len (length values))
(deferred:message "PARALLEL COLLECTED")
(deferred:post-task nd 'ok (nreverse values)))
nil)))
(deferred:error cd
(lambda (e)
(push (cons name e) values)
(deferred:message "PARALLEL ERROR [%s/%s] %s"
(length values) len (cons name e))
(when (= (length values) len)
(deferred:message "PARALLEL COLLECTED")
(deferred:post-task nd 'ok (nreverse values)))
nil))))
nd))
(defun deferred:parallel-list (lst)
"[internal] Deferred list implementation for `deferred:parallel'. "
(deferred:message "PARALLEL<LIST>" )
(lexical-let*
((pd (deferred:parallel-main (deferred:parallel-array-to-alist lst)))
(rd (deferred:nextc pd 'deferred:parallel-alist-to-array)))
(setf (deferred-cancel rd)
(lambda (x) (deferred:default-cancel x)
(deferred:cancel pd)))
rd))
(defun deferred:parallel (&rest args)
"Return a deferred object that calls given deferred objects or
functions in parallel and wait for all callbacks. The following
deferred task will be called with an array of the return
values. ARGS can be a list or an alist of deferred objects or
functions."
(deferred:message "PARALLEL : %s" args)
(deferred:trans-multi-args args
'deferred:parallel 'deferred:parallel-list 'deferred:parallel-main))
(defun deferred:earlier-main (alst)
"[internal] Deferred alist implementation for `deferred:earlier'. "
(deferred:message "EARLIER<KEY . VALUE>" )
(lexical-let ((nd (deferred:new))
(len (length alst))
value results)
(loop for pair in
(deferred:parallel-func-to-deferred alst)
with cd ; current child deferred
do
(lexical-let ((name (car pair)))
(setq cd
(deferred:nextc (cdr pair)
(lambda (x)
(push (cons name x) results)
(cond
((null value)
(setq value (cons name x))
(deferred:message "EARLIER VALUE %s" (cons name value))
(deferred:post-task nd 'ok value))
(t
(deferred:message "EARLIER MISS [%s/%s] %s" (length results) len (cons name value))
(when (eql (length results) len)
(deferred:message "EARLIER COLLECTED"))))
nil)))
(deferred:error cd
(lambda (e)
(push (cons name e) results)
(deferred:message "EARLIER ERROR [%s/%s] %s" (length results) len (cons name e))
(when (and (eql (length results) len) (null value))
(deferred:message "EARLIER FAILED")
(deferred:post-task nd 'ok nil))
nil))))
nd))
(defun deferred:earlier-list (lst)
"[internal] Deferred list implementation for `deferred:earlier'. "
(deferred:message "EARLIER<LIST>" )
(lexical-let*
((pd (deferred:earlier-main (deferred:parallel-array-to-alist lst)))
(rd (deferred:nextc pd (lambda (x) (cdr x)))))
(setf (deferred-cancel rd)
(lambda (x) (deferred:default-cancel x)
(deferred:cancel pd)))
rd))
(defun deferred:earlier (&rest args)
"Return a deferred object that calls given deferred objects or
functions in parallel and wait for the first callback. The
following deferred task will be called with the first return
value. ARGS can be a list or an alist of deferred objects or
functions."
(deferred:message "EARLIER : %s" args)
(deferred:trans-multi-args args
'deferred:earlier 'deferred:earlier-list 'deferred:earlier-main))
(defmacro deferred:timeout (timeout-msec timeout-form d)
"Time out macro on a deferred task D. If the deferred task D
does not complete within TIMEOUT-MSEC, this macro cancels the
deferred task and return the TIMEOUT-FORM."
`(deferred:earlier
(deferred:nextc (deferred:wait ,timeout-msec)
(lambda (x) ,timeout-form))
,d))
(defmacro* deferred:try (d &key catch finally)
"Try-catch-finally macro. This macro simulates the
try-catch-finally block asynchronously. CATCH and FINALLY can be
nil. Because of asynchrony, this macro does not ensure that the
task FINALLY should be called."
(let ((chain
(if catch `((deferred:error it ,catch)))))
(when finally
(setq chain (append chain `((deferred:watch it ,finally)))))
`(deferred:$ ,d ,@chain)))
;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;
;; Application functions
(defvar deferred:uid 0 "[internal] Sequence number for some utilities. See the function `deferred:uid'.")
(defun deferred:uid ()
"[internal] Generate a sequence number."
(incf deferred:uid))
(defun deferred:buffer-string (strformat buf)
"[internal] Return a string in the buffer with the given format."
(format strformat
(with-current-buffer buf (buffer-string))))
(defun deferred:process (command &rest args)
"A deferred wrapper of `start-process'. Return a deferred
object. The process name and buffer name of the argument of the
`start-process' are generated by this function automatically.
The next deferred object receives stdout string from the command
process."
(deferred:process-gen 'start-process command args))
(defun deferred:process-shell (command &rest args)
"A deferred wrapper of `start-process-shell-command'. Return a deferred
object. The process name and buffer name of the argument of the
`start-process-shell-command' are generated by this function automatically.
The next deferred object receives stdout string from the command
process."
(deferred:process-gen 'start-process-shell-command command args))
(defun deferred:process-buffer (command &rest args)
"A deferred wrapper of `start-process'. Return a deferred
object. The process name and buffer name of the argument of the
`start-process' are generated by this function automatically.
The next deferred object receives stdout buffer from the command
process."
(deferred:process-buffer-gen 'start-process command args))
(defun deferred:process-shell-buffer (command &rest args)
"A deferred wrapper of `start-process-shell-command'. Return a deferred
object. The process name and buffer name of the argument of the
`start-process-shell-command' are generated by this function automatically.
The next deferred object receives stdout buffer from the command
process."
(deferred:process-buffer-gen 'start-process-shell-command command args))
(defun deferred:process-gen (f command args)
"[internal]"
(lexical-let
((pd (deferred:process-buffer-gen f command args)) d)
(setq d (deferred:nextc pd
(lambda (buf)
(prog1
(with-current-buffer buf (buffer-string))
(kill-buffer buf)))))
(setf (deferred-cancel d)
(lambda (x)
(deferred:default-cancel d)
(deferred:default-cancel pd)))
d))
(defun deferred:process-buffer-gen (f command args)
"[internal]"
(let ((d (deferred:next)) (uid (deferred:uid)))
(lexical-let
((f f) (command command) (args args)
(proc-name (format "*deferred:*%s*:%s" command uid))
(buf-name (format " *deferred:*%s*:%s" command uid))
(pwd default-directory)
(nd (deferred:new)) proc-buf proc)
(deferred:nextc d
(lambda (x)
(setq proc-buf (get-buffer-create buf-name))
(condition-case err
(let ((default-directory pwd))
(setq proc
(if (null (car args))
(apply f proc-name buf-name command nil)
(apply f proc-name buf-name command args)))
(set-process-sentinel
proc
(lambda (proc event)
(cond
((string-match "exited abnormally" event)
(let ((msg (if (buffer-live-p proc-buf)
(deferred:buffer-string
(format "Process [%s] exited abnormally : %%s"
command) proc-buf)
(concat "Process exited abnormally: " proc-name))))
(kill-buffer proc-buf)
(deferred:post-task nd 'ng msg)))
((equal event "finished\n")
(deferred:post-task nd 'ok proc-buf)))))
(setf (deferred-cancel nd)
(lambda (x) (deferred:default-cancel x)
(when proc
(kill-process proc)
(kill-buffer proc-buf)))))
(error (deferred:post-task nd 'ng (error-message-string err))))
nil))
nd)))
(defmacro deferred:processc (d command &rest args)
"Process chain of `deferred:process'."
`(deferred:nextc ,d
(lambda (,(gensym)) (deferred:process ,command ,@args))))
(defmacro deferred:process-bufferc (d command &rest args)
"Process chain of `deferred:process-buffer'."
`(deferred:nextc ,d
(lambda (,(gensym)) (deferred:process-buffer ,command ,@args))))
(defmacro deferred:process-shellc (d command &rest args)
"Process chain of `deferred:process'."
`(deferred:nextc ,d
(lambda (,(gensym)) (deferred:process-shell ,command ,@args))))
(defmacro deferred:process-shell-bufferc (d command &rest args)
"Process chain of `deferred:process-buffer'."
`(deferred:nextc ,d
(lambda (,(gensym)) (deferred:process-shell-buffer ,command ,@args))))
(eval-after-load "url"
;; for url package
;; TODO: proxy, charaset
'(progn
(defun deferred:url-retrieve (url &optional cbargs)
"A wrapper function for url-retrieve. The next deferred
object receives the buffer object that URL will load
into. Currently dynamic binding variables are not supported."
(lexical-let ((nd (deferred:new)) (url url) (cbargs cbargs) buf)
(deferred:next
(lambda (x)
(condition-case err
(setq buf
(url-retrieve
url (lambda (xx) (deferred:post-task nd 'ok buf))
cbargs))
(error (deferred:post-task nd 'ng err)))
nil))
(setf (deferred-cancel nd)
(lambda (x)
(when (buffer-live-p buf)
(kill-buffer buf))))
nd))
(defun deferred:url-delete-header (buf)
(with-current-buffer buf
(let ((pos (url-http-symbol-value-in-buffer
'url-http-end-of-headers buf)))
(when pos
(delete-region (point-min) (1+ pos)))))
buf)
(defun deferred:url-delete-buffer (buf)
(when (and buf (buffer-live-p buf))
(kill-buffer buf))
nil)
(defun deferred:url-get (url &optional params)
"Perform a HTTP GET method with `url-retrieve'. PARAMS is
a parameter list of (key . value) or key. The next deferred
object receives the buffer object that URL will load into."
(when params
(setq url
(concat url "?" (deferred:url-param-serialize params))))
(let ((d (deferred:$
(deferred:url-retrieve url)
(deferred:nextc it 'deferred:url-delete-header))))
(deferred:set-next
d (deferred:new 'deferred:url-delete-buffer))
d))
(defun deferred:url-post (url &optional params)
"Perform a HTTP POST method with `url-retrieve'. PARAMS is
a parameter list of (key . value) or key. The next deferred
object receives the buffer object that URL will load into."
(lexical-let ((nd (deferred:new))
(url url) (params params)
buf)
(deferred:next
(lambda (x)
(let ((url-request-method "POST")
(url-request-extra-headers
'(("Content-Type" . "application/x-www-form-urlencoded")))
(url-request-data
(deferred:url-param-serialize params)))
(condition-case err
(setq buf
(url-retrieve
url
(lambda (&rest args)
(deferred:post-task nd 'ok buf))))
(error (deferred:post-task nd 'ng err))))
nil))
(setf (deferred-cancel nd)
(lambda (x)
(when (buffer-live-p buf)
(kill-buffer buf))))
(let ((d (deferred:nextc nd 'deferred:url-delete-header)))
(deferred:set-next
d (deferred:new 'deferred:url-delete-buffer))
d)))
(defun deferred:url-escape (val)
"[internal] Return a new string that is VAL URI-encoded."
(unless (stringp val)
(setq val (format "%s" val)))
(url-hexify-string
(encode-coding-string val 'utf-8)))
(defun deferred:url-param-serialize (params)
"[internal] Serialize a list of (key . value) cons cells
into a query string."
(when params
(mapconcat
'identity
(loop for p in params
collect
(cond
((consp p)
(concat
(deferred:url-escape (car p)) "="
(deferred:url-escape (cdr p))))
(t
(deferred:url-escape p))))
"&")))
))
(provide 'deferred)
;;; deferred.el ends here

911
conf/emacs.d/epc.el Normal file
View File

@ -0,0 +1,911 @@
;;; epc.el --- A RPC stack for the Emacs Lisp
;; Copyright (C) 2011, 2012 Masashi Sakurai
;; Author: SAKURAI Masashi <m.sakurai at kiwanami.net>
;; Version: 0.1.1
;; Keywords: lisp, rpc
;; Package-Requires: ((concurrent "0.3.1") (ctable "0.1.1"))
;; URL: https://github.com/kiwanami/emacs-epc
;; This program 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.
;; This program 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 this program. If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;; This program is an asynchronous RPC stack for Emacs. Using this
;; RPC stack, the Emacs can communicate with the peer process.
;; Because the protocol is S-expression encoding and consists of
;; asynchronous communications, the RPC response is fairly good.
;;
;; Current implementations for the EPC are followings:
;; - epcs.el : Emacs Lisp implementation
;; - RPC::EPC::Service : Perl implementation
;;; Code:
(eval-when-compile (require 'cl))
(require 'concurrent)
(require 'ctable)
;;==================================================
;; Utility
(defvar epc:debug-out nil)
(defvar epc:debug-buffer "*epc log*")
;;(setq epc:debug-out t)
;;(setq epc:debug-out nil)
(defun epc:log-init ()
(when (get-buffer epc:debug-buffer)
(kill-buffer epc:debug-buffer)))
(defun epc:log (&rest args)
(when epc:debug-out
(with-current-buffer
(get-buffer-create epc:debug-buffer)
(buffer-disable-undo)
(goto-char (point-max))
(insert (apply 'format args) "\n"))))
(defun epc:make-procbuf (name)
"[internal] Make a process buffer."
(let ((buf (get-buffer-create name)))
(with-current-buffer buf
(set (make-local-variable 'kill-buffer-query-functions) nil)
(erase-buffer) (buffer-disable-undo))
buf))
(defun epc:document-function (function docstring)
"Document FUNCTION with DOCSTRING. Use this for `defstruct' accessor etc."
(put function 'function-documentation docstring))
(put 'epc:document-function 'lisp-indent-function 'defun)
(put 'epc:document-function 'doc-string-elt 2)
;;==================================================
;; Low Level Interface
(defvar epc:uid 1)
(defun epc:uid ()
(incf epc:uid))
(defvar epc:accept-process-timeout 100 "[internal] msec")
(defstruct epc:connection
"Set of information for network connection and event handling.
name : Connection name. This name is used for process and buffer names.
process : Connection process object.
buffer : Working buffer for the incoming data.
channel : Event channels for incoming messages."
name process buffer channel)
(epc:document-function 'epc:connection-name
"[internal] Connection name. This name is used for process and buffer names.
\(fn EPC:CONNECTION)")
(epc:document-function 'epc:connection-process
"[internal] Connection process object.
\(fn EPC:CONNECTION)")
(epc:document-function 'epc:connection-buffer
"[internal] Working buffer for the incoming data.
\(fn EPC:CONNECTION)")
(epc:document-function 'epc:connection-channel
"[internal] Event channels for incoming messages.
\(fn EPC:CONNECTION)")
(defun epc:connect (host port)
"[internal] Connect the server, initialize the process and
return epc:connection object."
(epc:log ">> Connection start: %s:%s" host port)
(lexical-let* ((connection-id (epc:uid))
(connection-name (format "epc con %s" connection-id))
(connection-buf (epc:make-procbuf (format "*%s*" connection-name)))
(connection-process
(open-network-stream connection-name connection-buf host port))
(channel (cc:signal-channel connection-name))
(connection (make-epc:connection
:name connection-name
:process connection-process
:buffer connection-buf
:channel channel)))
(epc:log ">> Connection establish")
(set-process-coding-system connection-process 'binary 'binary)
(set-process-filter connection-process
(lambda (p m)
(epc:process-filter connection p m)))
(set-process-sentinel connection-process
(lambda (p e)
(epc:process-sentinel connection p e)))
(set-process-query-on-exit-flag connection-process nil)
connection))
(defun epc:connection-reset (connection)
"[internal] Reset the connection for restarting the process."
(cc:signal-disconnect-all (epc:connection-channel connection))
connection)
(defun epc:process-sentinel (connection process msg)
(epc:log "!! Process Sentinel [%s] : %S : %S"
(epc:connection-name connection) process msg)
(epc:disconnect connection))
(defun epc:net-send (connection sexp)
(let* ((msg (encode-coding-string
(concat (epc:prin1-to-string sexp) "\n") 'utf-8-unix))
(string (concat (epc:net-encode-length (length msg)) msg))
(proc (epc:connection-process connection)))
(epc:log ">> SEND : [%S]" string)
(process-send-string proc string)))
(defun epc:disconnect (connection)
(lexical-let
((process (epc:connection-process connection))
(buf (epc:connection-buffer connection))
(name (epc:connection-name connection)))
(epc:log "!! Disconnect [%s]" name)
(when process
(set-process-sentinel process nil)
(delete-process process)
(when (get-buffer buf) (kill-buffer buf)))
(epc:log "!! Disconnected finished [%s]" name)))
(defun epc:process-filter (connection process message)
(epc:log "INCOMING: [%s] [%S]" (epc:connection-name connection) message)
(with-current-buffer (epc:connection-buffer connection)
(goto-char (point-max))
(insert message)
(epc:process-available-input connection process)))
(defun epc:process-available-input (connection process)
"Process all complete messages that have arrived from Lisp."
(with-current-buffer (process-buffer process)
(while (epc:net-have-input-p)
(let ((event (epc:net-read-or-lose process))
(ok nil))
(epc:log "<< RECV [%S]" event)
(unwind-protect
(condition-case err
(progn
(apply 'cc:signal-send
(cons (epc:connection-channel connection) event))
(setq ok t))
('error (epc:log "MsgError: %S / <= %S" err event)))
(unless ok
(epc:run-when-idle 'epc:process-available-input connection process)))))))
(defun epc:net-have-input-p ()
"Return true if a complete message is available."
(goto-char (point-min))
(and (>= (buffer-size) 6)
(>= (- (buffer-size) 6) (epc:net-decode-length))))
(defun epc:run-when-idle (function &rest args)
"Call FUNCTION as soon as Emacs is idle."
(apply #'run-at-time
(if (featurep 'xemacs) itimer-short-interval 0)
nil function args))
(defun epc:net-read-or-lose (process)
(condition-case error
(epc:net-read)
(error
(debug 'error error)
(error "net-read error: %S" error))))
(defun epc:net-read ()
"Read a message from the network buffer."
(goto-char (point-min))
(let* ((length (epc:net-decode-length))
(start (+ 6 (point)))
(end (+ start length)) content)
(assert (plusp length))
(prog1 (save-restriction
(narrow-to-region start end)
(read (decode-coding-string
(buffer-string) 'utf-8-unix)))
(delete-region (point-min) end))))
(defun epc:net-decode-length ()
"Read a 24-bit hex-encoded integer from buffer."
(string-to-number (buffer-substring-no-properties (point) (+ (point) 6)) 16))
(defun epc:net-encode-length (n)
"Encode an integer into a 24-bit hex string."
(format "%06x" n))
(defun epc:prin1-to-string (sexp)
"Like `prin1-to-string' but don't octal-escape non-ascii characters.
This is more compatible with the CL reader."
(with-temp-buffer
(let (print-escape-nonascii
print-escape-newlines
print-length
print-level)
(prin1 sexp (current-buffer))
(buffer-string))))
;;==================================================
;; High Level Interface
(defstruct epc:manager
"Root object that holds all information related to an EPC activity.
`epc:start-epc' returns this object.
title : instance name for displaying on the `epc:controller' UI
server-process : process object for the peer
commands : a list of (prog . args)
port : port number
connection : epc:connection instance
methods : alist of method (name . function)
sessions : alist of session (id . deferred)
exit-hook : functions for after shutdown EPC connection"
title server-process commands port connection methods sessions exit-hooks)
(epc:document-function 'epc:manager-title
"Instance name (string) for displaying on the `epc:controller' UI
You can modify this slot using `setf' to change the title column
in the `epc:controller' table UI.
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-server-process
"Process object for the peer.
This is *not* network process but the external program started by
`epc:start-epc'. For network process, see `epc:connection-process'.
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-commands
"[internal] a list of (prog . args)
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-port
"Port number (integer).
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-connection
"[internal] epc:connection instance
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-methods
"[internal] alist of method (name . function)
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-sessions
"[internal] alist of session (id . deferred)
\(fn EPC:MANAGER)")
(epc:document-function 'epc:manager-exit-hooks
"Hooks called after shutdown EPC connection.
Use `epc:manager-add-exit-hook' to add hook.
\(fn EPC:MANAGER)")
(defstruct epc:method
"Object to hold serving method information.
name : method name (symbol) ex: 'test
task : method function (function with one argument)
arg-specs : arg-specs (one string) ex: \"(A B C D)\"
docstring : docstring (one string) ex: \"A test function. Return sum of A,B,C and D\"
"
name task docstring arg-specs)
(epc:document-function 'epc:method-name
"[internal] method name (symbol) ex: 'test
\(fn EPC:METHOD)")
(epc:document-function 'epc:method-task
"[internal] method function (function with one argument)
\(fn EPC:METHOD)")
(epc:document-function 'epc:method-arg-specs
"[internal] arg-specs (one string) ex: \"(A B C D)\"
\(fn EPC:METHOD)")
(epc:document-function 'epc:method-docstring
"[internal] docstring (one string) ex: \"A test function. Return sum of A,B,C and D\"
\(fn EPC:METHOD)")
(defvar epc:live-connections nil
"[internal] A list of `epc:manager' objects those currently connect to the epc peer.
This variable is for debug purpose.")
(defun epc:live-connections-add (mngr)
"[internal] Add the EPC manager object."
(push mngr epc:live-connections))
(defun epc:live-connections-delete (mngr)
"[internal] Remove the EPC manager object."
(setq epc:live-connections (delete mngr epc:live-connections)))
(defun epc:start-epc (server-prog server-args)
"Start the epc server program and return an epc:manager object.
Start server program SERVER-PROG with command line arguments
SERVER-ARGS. The server program must print out the port it is
using at the first line of its stdout. If the server prints out
non-numeric value in the first line or does not print out the
port number in three seconds, it is regarded as start-up
failure."
(let ((mngr (epc:start-server server-prog server-args)))
(epc:init-epc-layer mngr)
mngr))
(defun epc:server-process-name (uid)
(format "epc:server:%s" uid))
(defun epc:server-buffer-name (uid)
(format " *%s*" (epc:server-process-name uid)))
(defun epc:start-server (server-prog server-args)
"[internal] Start a peer server and return an epc:manager instance which is set up partially."
(let* ((uid (epc:uid))
(process-name (epc:server-process-name uid))
(process-buffer (get-buffer-create (epc:server-buffer-name uid)))
(process (apply 'start-process
process-name process-buffer
server-prog server-args))
(cont 1) port)
(while cont
(accept-process-output process 0 epc:accept-process-timeout t)
(let ((port-str (with-current-buffer process-buffer
(buffer-string))))
(cond
((string-match "^[0-9]+$" port-str)
(setq port (string-to-number port-str)
cont nil))
((< 0 (length port-str))
(error "Server may raise an error. \
Use \"M-x epc:pop-to-last-server-process-buffer RET\" \
to see full traceback:\n%s" port-str))
((not (eq 'run (process-status process)))
(setq cont nil))
(t
(incf cont)
(when (< 30 cont) ; timeout 3 seconds
(error "Timeout server response."))))))
(set-process-query-on-exit-flag process nil)
(make-epc:manager :server-process process
:commands (cons server-prog server-args)
:title (mapconcat 'identity (cons server-prog server-args) " ")
:port port
:connection (epc:connect "localhost" port))))
(defun epc:stop-epc (mngr)
"Disconnect the connection for the server."
(let* ((proc (epc:manager-server-process mngr))
(buf (and proc (process-buffer proc))))
(epc:disconnect (epc:manager-connection mngr))
(when proc
(accept-process-output proc 0 epc:accept-process-timeout t))
(when (and proc (equal 'run (process-status proc)))
(kill-process proc))
(when buf (kill-buffer buf))
(condition-case err
(epc:manager-fire-exit-hook mngr)
(error (epc:log "Error on exit-hooks : %S / " err mngr)))
(epc:live-connections-delete mngr)))
(defun epc:start-epc-debug (port)
"[internal] Return an epc:manager instance which is set up partially."
(epc:init-epc-layer
(make-epc:manager :server-process nil
:commands (cons "[DEBUG]" nil)
:port port
:connection (epc:connect "localhost" port))))
(defun epc:args (args)
"[internal] If ARGS is an atom, return it. If list, return the cadr of it."
(cond
((atom args) args)
(t (cadr args))))
(defun epc:init-epc-layer (mngr)
"[internal] Connect to the server program and return an epc:connection instance."
(lexical-let*
((mngr mngr)
(conn (epc:manager-connection mngr))
(channel (epc:connection-channel conn)))
;; dispatch incoming messages with the lexical scope
(loop for (method . body) in
`((call
. (lambda (args)
(epc:log "SIG CALL: %S" args)
(apply 'epc:handler-called-method ,mngr (epc:args args))))
(return
. (lambda (args)
(epc:log "SIG RET: %S" args)
(apply 'epc:handler-return ,mngr (epc:args args))))
(return-error
. (lambda (args)
(epc:log "SIG RET-ERROR: %S" args)
(apply 'epc:handler-return-error ,mngr (epc:args args))))
(epc-error
. (lambda (args)
(epc:log "SIG EPC-ERROR: %S" args)
(apply 'epc:handler-epc-error ,mngr (epc:args args))))
(methods
. (lambda (args)
(epc:log "SIG METHODS: %S" args)
(epc:handler-methods ,mngr (caadr args))))
) do
(cc:signal-connect channel method body))
(epc:live-connections-add mngr)
mngr))
(defun epc:manager-add-exit-hook (mngr hook-function)
"Register the HOOK-FUNCTION which is called after the EPC connection closed by the EPC controller UI.
HOOK-FUNCTION is a function with no argument."
(let* ((hooks (epc:manager-exit-hooks mngr)))
(setf (epc:manager-exit-hooks mngr) (cons hook-function hooks))
mngr))
(defun epc:manager-fire-exit-hook (mngr)
"[internal] Call exit-hooks functions of MNGR. After calling hooks, this functions clears the hook slot so as not to call doubly."
(let* ((hooks (epc:manager-exit-hooks mngr)))
(run-hooks hooks)
(setf (epc:manager-exit-hooks mngr) nil)
mngr))
(defun epc:manager-status-server-process (mngr)
"[internal] Return the status of the process object for the peer process. If the process is nil, return nil."
(and mngr
(epc:manager-server-process mngr)
(process-status (epc:manager-server-process mngr))))
(defun epc:manager-status-connection-process (mngr)
"[internal] Return the status of the process object for the connection process."
(and (epc:manager-connection mngr)
(process-status (epc:connection-process
(epc:manager-connection mngr)))))
(defun epc:manager-restart-process (mngr)
"[internal] Restart the process and reconnect."
(cond
((null (epc:manager-server-process mngr))
(error "Cannot restart this EPC process!"))
(t
(epc:stop-epc mngr)
(let* ((cmds (epc:manager-commands mngr))
(new-mngr (epc:start-server (car cmds) (cdr cmds))))
(setf (epc:manager-server-process mngr)
(epc:manager-server-process new-mngr))
(setf (epc:manager-port mngr)
(epc:manager-port new-mngr))
(setf (epc:manager-connection mngr)
(epc:manager-connection new-mngr))
(setf (epc:manager-methods mngr)
(epc:manager-methods new-mngr))
(setf (epc:manager-sessions mngr)
(epc:manager-sessions new-mngr))
(epc:connection-reset (epc:manager-connection mngr))
(epc:init-epc-layer mngr)
(epc:live-connections-delete new-mngr)
(epc:live-connections-add mngr)
mngr))))
(defun epc:manager-send (mngr method &rest messages)
"[internal] low-level message sending."
(let* ((conn (epc:manager-connection mngr)))
(epc:net-send conn (cons method messages))))
(defun epc:manager-get-method (mngr method-name)
"[internal] Return a method object. If not found, return nil."
(loop for i in (epc:manager-methods mngr)
if (eq method-name (epc:method-name i))
do (return i)))
(defun epc:handler-methods (mngr uid)
"[internal] Return a list of information for registered methods."
(let ((info
(loop for i in (epc:manager-methods mngr)
collect
(list
(epc:method-name i)
(or (epc:method-arg-specs i) "")
(or (epc:method-docstring i) "")))))
(epc:manager-send mngr 'return uid info)))
(defun epc:handler-called-method (mngr uid name args)
"[internal] low-level message handler for peer's calling."
(lexical-let ((mngr mngr) (uid uid))
(let* ((methods (epc:manager-methods mngr))
(method (epc:manager-get-method mngr name)))
(cond
((null method)
(epc:log "ERR: No such method : %s" name)
(epc:manager-send mngr 'epc-error uid (format "EPC-ERROR: No such method : %s" name)))
(t
(condition-case err
(let* ((f (epc:method-task method))
(ret (apply f args)))
(cond
((deferred-p ret)
(deferred:nextc ret
(lambda (xx) (epc:manager-send mngr 'return uid xx))))
(t (epc:manager-send mngr 'return uid ret))))
(error
(epc:log "ERROR : %S" err)
(epc:manager-send mngr 'return-error uid err))))))))
(defun epc:manager-remove-session (mngr uid)
"[internal] Remove a session from the epc manager object."
(loop with ret = nil
for pair in (epc:manager-sessions mngr)
unless (eq uid (car pair))
do (push pair ret)
finally
do (setf (epc:manager-sessions mngr) ret)))
(defun epc:handler-return (mngr uid args)
"[internal] low-level message handler for normal returns."
(let ((pair (assq uid (epc:manager-sessions mngr))))
(cond
(pair
(epc:log "RET: id:%s [%S]" uid args)
(epc:manager-remove-session mngr uid)
(deferred:callback (cdr pair) args))
(t ; error
(epc:log "RET: NOT FOUND: id:%s [%S]" uid args)))))
(defun epc:handler-return-error (mngr uid args)
"[internal] low-level message handler for application errors."
(let ((pair (assq uid (epc:manager-sessions mngr))))
(cond
(pair
(epc:log "RET-ERR: id:%s [%S]" uid args)
(epc:manager-remove-session mngr uid)
(deferred:errorback (cdr pair) (format "%S" args)))
(t ; error
(epc:log "RET-ERR: NOT FOUND: id:%s [%S]" uid args)))))
(defun epc:handler-epc-error (mngr uid args)
"[internal] low-level message handler for epc errors."
(let ((pair (assq uid (epc:manager-sessions mngr))))
(cond
(pair
(epc:log "RET-EPC-ERR: id:%s [%S]" uid args)
(epc:manager-remove-session mngr uid)
(deferred:errorback (cdr pair) (list 'epc-error args)))
(t ; error
(epc:log "RET-EPC-ERR: NOT FOUND: id:%s [%S]" uid args)))))
(defun epc:call-deferred (mngr method-name args)
"Call peer's method with args asynchronously. Return a deferred
object which is called with the result."
(let ((uid (epc:uid))
(sessions (epc:manager-sessions mngr))
(d (deferred:new)))
(push (cons uid d) sessions)
(setf (epc:manager-sessions mngr) sessions)
(epc:manager-send mngr 'call uid method-name args)
d))
(defun epc:define-method (mngr method-name task &optional arg-specs docstring)
"Define a method and return a deferred object which is called by the peer."
(let* ((method (make-epc:method
:name method-name :task task
:arg-specs arg-specs :docstring docstring))
(methods (cons method (epc:manager-methods mngr))))
(setf (epc:manager-methods mngr) methods)
method))
(defun epc:query-methods-deferred (mngr)
"Return a list of information for the peer's methods.
The list is consisted of lists of strings:
(name arg-specs docstring)."
(let ((uid (epc:uid))
(sessions (epc:manager-sessions mngr))
(d (deferred:new)))
(push (cons uid d) sessions)
(setf (epc:manager-sessions mngr) sessions)
(epc:manager-send mngr 'methods uid)
d))
(defun epc:sync (mngr d)
"Wrap deferred methods with synchronous waiting, and return the result.
If an exception is occurred, this function throws the error."
(lexical-let ((result 'epc:nothing))
(deferred:$ d
(deferred:nextc it
(lambda (x) (setq result x)))
(deferred:error it
(lambda (er) (setq result (cons 'error er)))))
(while (eq result 'epc:nothing)
(save-current-buffer
(accept-process-output
(epc:connection-process (epc:manager-connection mngr))
0 epc:accept-process-timeout t)))
(if (and (consp result) (eq 'error (car result)))
(error (cdr result)) result)))
(defun epc:call-sync (mngr method-name args)
"Call peer's method with args synchronously and return the result.
If an exception is occurred, this function throws the error."
(epc:sync mngr (epc:call-deferred mngr method-name args)))
(defun epc:live-p (mngr)
"Return non-nil when MNGR is an EPC manager object with a live
connection."
(let ((proc (ignore-errors
(epc:connection-process (epc:manager-connection mngr)))))
(and (processp proc)
;; Same as `process-live-p' in Emacs >= 24:
(memq (process-status proc) '(run open listen connect stop)))))
;;==================================================
;; Troubleshooting / Debugging support
(defun epc:pop-to-last-server-process-buffer ()
"Open the buffer for most recently started server program process.
This is useful when you want to check why the server program
failed to start (e.g., to see its traceback / error message)."
(interactive)
(let ((buffer (get-buffer (epc:server-buffer-name epc:uid))))
(if buffer
(pop-to-buffer buffer)
(error "No buffer for the last server process. \
Probably the EPC connection exits correctly or you didn't start it yet."))))
;;==================================================
;; Management Interface
(defun epc:controller ()
"Display the management interface for EPC processes and connections.
Process list.
Session status, statistics and uptime.
Peer's method list.
Display process buffer.
Kill sessions and connections.
Restart process."
(interactive)
(let* ((buf-name "*EPC Controller*")
(buf (get-buffer buf-name)))
(unless (buffer-live-p buf)
(setq buf (get-buffer-create buf-name)))
(epc:controller-update-buffer buf)
(pop-to-buffer buf)))
(defun epc:controller-update-buffer (buf)
"[internal] Update buffer for the current epc processes."
(let*
((data (loop
for mngr in epc:live-connections collect
(list
(epc:manager-server-process mngr)
(epc:manager-status-server-process mngr)
(epc:manager-status-connection-process mngr)
(epc:manager-title mngr)
(epc:manager-commands mngr)
(epc:manager-port mngr)
(length (epc:manager-methods mngr))
(length (epc:manager-sessions mngr))
mngr)))
(param (copy-ctbl:param ctbl:default-rendering-param))
(cp
(ctbl:create-table-component-buffer
:buffer buf :width nil
:model
(make-ctbl:model
:column-model
(list (make-ctbl:cmodel :title "<Process>" :align 'left)
(make-ctbl:cmodel :title "<Proc>" :align 'center)
(make-ctbl:cmodel :title "<Conn>" :align 'center)
(make-ctbl:cmodel :title " Title " :align 'left :max-width 30)
(make-ctbl:cmodel :title " Command " :align 'left :max-width 30)
(make-ctbl:cmodel :title " Port " :align 'right)
(make-ctbl:cmodel :title " Methods " :align 'right)
(make-ctbl:cmodel :title " Live sessions " :align 'right))
:data data)
:custom-map epc:controller-keymap
:param param)))
(pop-to-buffer (ctbl:cp-get-buffer cp))))
(eval-when-compile ; introduce anaphoric variable `cp' and `mngr'.
(defmacro epc:controller-with-cp (&rest body)
`(let ((cp (ctbl:cp-get-component)))
(when cp
(let ((mngr (car (last (ctbl:cp-get-selected-data-row cp)))))
,@body)))))
(defun epc:controller-update-command ()
(interactive)
(epc:controller-with-cp
(epc:controller-update-buffer (current-buffer))))
(defun epc:controller-connection-restart-command ()
(interactive)
(epc:controller-with-cp
(let* ((proc (epc:manager-server-process mngr))
(msg (format "Restart the EPC process [%s] ? " proc)))
(when (and proc (y-or-n-p msg))
(epc:manager-restart-process mngr)
(epc:controller-update-buffer (current-buffer))))))
(defun epc:controller-connection-kill-command ()
(interactive)
(epc:controller-with-cp
(let* ((proc (epc:manager-server-process mngr))
(msg (format "Kill the EPC process [%s] ? " proc)))
(when (and proc (y-or-n-p msg))
(epc:stop-epc mngr)
(epc:controller-update-buffer (current-buffer))))))
(defun epc:controller-connection-buffer-command ()
(interactive)
(epc:controller-with-cp
(switch-to-buffer
(epc:connection-buffer (epc:manager-connection mngr)))))
(defun epc:controller-methods-show-command ()
(interactive)
(epc:controller-with-cp
(epc:controller-methods mngr)))
(defun epc:controller-methods (mngr)
"Display a list of methods for the MNGR process."
(let* ((buf-name "*EPC Controller/Methods*")
(buf (get-buffer buf-name)))
(unless (buffer-live-p buf)
(setq buf (get-buffer-create buf-name))
(with-current-buffer buf
(setq buffer-read-only t)))
(lexical-let ((buf buf) (mngr mngr))
(deferred:$
(epc:query-methods-deferred mngr)
(deferred:nextc it
(lambda (methods)
(epc:controller-methods-update-buffer buf mngr methods)
(pop-to-buffer buf)))))))
(defface epc:face-title
'((((class color) (background light))
:foreground "Slategray4" :background "Gray90" :weight bold)
(((class color) (background dark))
:foreground "maroon2" :weight bold))
"Face for titles" :group 'epc)
(defun epc:controller-methods-update-buffer (buf mngr methods)
"[internal] Update methods list buffer for the epc process."
(with-current-buffer buf
(let* ((data
(loop for m in methods collect
(list
(car m)
(or (nth 1 m) "<Not specified>")
(or (nth 2 m) "<Not specified>"))))
(param (copy-ctbl:param ctbl:default-rendering-param))
cp buffer-read-only)
(erase-buffer)
(insert
(propertize
(format "EPC Process : %s\n"
(mapconcat 'identity (epc:manager-commands mngr) " "))
'face 'epc:face-title) "\n")
(setq cp (ctbl:create-table-component-region
:model
(make-ctbl:model
:column-model
(list (make-ctbl:cmodel :title "Method Name" :align 'left)
(make-ctbl:cmodel :title "Arguments" :align 'left)
(make-ctbl:cmodel :title "Document" :align 'left))
:data data)
:keymap epc:controller-methods-keymap
:param param))
(set (make-local-variable 'epc:mngr) mngr)
(ctbl:cp-set-selected-cell cp '(0 . 0))
(ctbl:cp-get-buffer cp))))
(defun epc:controller-methods-eval-command ()
(interactive)
(let ((cp (ctbl:cp-get-component)))
(when cp
(let* ((method-name (car (ctbl:cp-get-selected-data-row cp)))
(args (eval-minibuffer
(format "Arguments for calling [%s] : " method-name))))
(deferred:$
(epc:call-deferred epc:mngr method-name args)
(deferred:nextc it
(lambda (ret) (message "Result : %S" ret)))
(deferred:error it
(lambda (err) (message "Error : %S" ret))))))))
(defun epc:define-keymap (keymap-list &optional prefix)
"[internal] Keymap utility."
(let ((map (make-sparse-keymap)))
(mapc
(lambda (i)
(define-key map
(if (stringp (car i))
(read-kbd-macro
(if prefix
(replace-regexp-in-string "prefix" prefix (car i))
(car i)))
(car i))
(cdr i)))
keymap-list)
map))
(defun epc:add-keymap (keymap keymap-list &optional prefix)
(loop with nkeymap = (copy-keymap keymap)
for i in keymap-list
do
(define-key nkeymap
(if (stringp (car i))
(read-kbd-macro
(if prefix
(replace-regexp-in-string "prefix" prefix (car i))
(car i)))
(car i))
(cdr i))
finally return nkeymap))
(defvar epc:controller-keymap
(epc:define-keymap
'(
("g" . epc:controller-update-command)
("R" . epc:controller-connection-restart-command)
("D" . epc:controller-connection-kill-command)
("K" . epc:controller-connection-kill-command)
("m" . epc:controller-methods-show-command)
("C-m" . epc:controller-methods-show-command)
("B" . epc:controller-connection-buffer-command)
)) "Keymap for the controller buffer.")
(defvar epc:controller-methods-keymap
(epc:add-keymap
ctbl:table-mode-map
'(
("q" . bury-buffer)
("e" . epc:controller-methods-eval-command)
)) "Keymap for the controller methods list buffer.")
(provide 'epc)
;;; epc.el ends here

995
conf/emacs.d/jedi.el Normal file
View File

@ -0,0 +1,995 @@
;;; jedi.el --- a Python auto-completion for Emacs
;; Copyright (C) 2012 Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; Package-Requires: ((epc "0.1.0") (auto-complete "1.4"))
;; Version: 0.1.2alpha2
;; This file is NOT part of GNU Emacs.
;; jedi.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.
;; jedi.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 jedi.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(require 'epc)
(require 'auto-complete)
(declare-function pos-tip-show "pos-tip")
(defgroup jedi nil
"Auto-completion for Python."
:group 'completion
:prefix "jedi:")
(defconst jedi:version "0.1.2alpha2")
(defvar jedi:source-dir (if load-file-name
(file-name-directory load-file-name)
default-directory))
(defvar jedi:epc nil)
(make-variable-buffer-local 'jedi:epc)
(defvar jedi:server-script
(convert-standard-filename
(expand-file-name "jediepcserver.py" jedi:source-dir))
"Full path to Jedi server script file ``jediepcserver.py``.")
;;; Configuration variables
(defcustom jedi:server-command
(list (let ((py (expand-file-name "env/bin/python" jedi:source-dir)))
(if (file-exists-p py) py "python"))
jedi:server-script)
"Command used to run Jedi server.
If you setup Jedi requirements using ``make requirements`` command,
`jedi:server-command' should be automatically set to::
'(\"JEDI:SOURCE-DIR/env/bin/python\"
\"JEDI:SOURCE-DIR/jediepcserver.py\")
Otherwise, it should be set to::
'(\"python\" \"JEDI:SOURCE-DIR/jediepcserver.py\")
If you want to use your favorite Python executable, set
`jedi:server-command' using::
(setq jedi:server-command
(list \"YOUR-FAVORITE-PYTHON\" jedi:server-script))
If you want to pass some arguments to the Jedi server command,
use `jedi:server-command'."
:group 'jedi)
(defcustom jedi:server-args nil
"Command line arguments to be appended to `jedi:server-command'.
If you want to add some special `sys.path' when starting Jedi
server, do something like this::
(setq jedi:server-args
'(\"--sys-path\" \"MY/SPECIAL/PATH\"
\"--sys-path\" \"MY/OTHER/SPECIAL/PATH\"))
If you want to include some virtualenv, do something like this.
Note that actual `VIRTUAL_ENV' is treated automatically. Also,
you need to start Jedi EPC server with the same python version
that you use for the virtualenv.::
(setq jedi:server-args
'(\"--virtual-env\" \"SOME/VIRTUAL_ENV_1\"
\"--virtual-env\" \"SOME/VIRTUAL_ENV_2\"))
To see what other arguments Jedi server can take, execute the
following command::
python jediepcserver.py --help
**Advanced usage**
Sometimes you want to configure how Jedi server is started per
buffer. To do that, you should make this variable buffer local
in `python-mode-hook' and set it to some buffer specific variable,
like this::
(defun my-jedi-server-setup ()
(let ((cmds (GET-SOME-PROJECT-SPECIFIC-COMMAND))
(args (GET-SOME-PROJECT-SPECIFIC-ARGS)))
(when cmds (set (make-local-variable 'jedi:server-command) cmds))
(when args (set (make-local-variable 'jedi:server-args) args))))
(add-hook 'python-mode-hook 'my-jedi-server-setup)
Note that Jedi server run by the same command is pooled. So,
there is only one Jedi server for the same set of command. If
you want to check how many EPC servers are running, use the EPC
GUI: M-x `epc:controller'. You will see a table of EPC connections
for Jedi.el and other EPC applications.
If you want to start a new ad-hoc server for the current buffer,
use the command `jedi:start-dedicated-server'."
:group 'jedi)
(defcustom jedi:complete-on-dot nil
"Non-`nil' means automatically start completion after inserting a dot.
To make this option work, you need to use `jedi:setup' instead of
`jedi:ac-setup' to start Jedi."
:group 'jedi)
(defcustom jedi:tooltip-method '(pos-tip popup)
"Configuration for `jedi:tooltip-show'.
This is a list which may contain symbol(s) `pos-tip' and/or
`popup'. It determines tooltip method to use. Setting this
value to nil means to use minibuffer instead of tooltip."
:group 'jedi)
(defcustom jedi:get-in-function-call-timeout 3000
"Cancel request to server for call signature after this period
specified in in millisecond."
:group 'jedi)
(defcustom jedi:get-in-function-call-delay 1000
"How long Jedi should wait before showing call signature
tooltip in millisecond."
:group 'jedi)
(defcustom jedi:goto-definition-config
'((nil nil nil)
(t nil nil)
(nil definition nil)
(t definition nil)
(nil nil t )
(t nil t )
(nil definition t )
(t definition t ))
"Configure how prefix argument modifies `jedi:goto-definition' behavior.
Each element of the list is arguments (list) passed to
`jedi:goto-definition'. Note that this variable has no effect on
`jedi:goto-definition' when it is used as a lisp function
The following setting is default (last parts are omitted).
Nth element is used as the argument when N universal prefix
arguments (``C-u``) are given.::
(setq jedi:goto-definition-config
'((nil nil nil) ; C-.
(t nil nil) ; C-u C-.
(nil definition nil) ; C-u C-u C-.
(t definition nil) ; C-u C-u C-u C-.
...))
For example, if you want to follow \"substitution path\" by default,
use the setting like this::
(setq jedi:goto-definition-config
'((nil definition nil)
(t definition nil)
(nil nil nil)
(t nil nil)
(nil definition t )
(t definition t )
(nil nil t )
(t nil t )))
You can rearrange the order to have most useful sets of arguments
at the top."
:group 'jedi)
(defcustom jedi:doc-mode 'rst-mode
"Major mode to use when showing document."
:group 'jedi)
(defcustom jedi:doc-hook '(view-mode)
"The hook that's run after showing a document."
:type 'hook
:group 'jedi)
(defcustom jedi:doc-display-buffer 'display-buffer
"A function to be called with a buffer to show document."
:group 'jedi)
(defcustom jedi:install-imenu nil
"[EXPERIMENTAL] If `t', use Jedi to create `imenu' index.
To use this feature, you need to install the developmental
version (\"dev\" branch) of Jedi."
:group 'jedi)
(defcustom jedi:setup-keys nil
"Setup recommended keybinds.
.. admonition:: Default keybinds
``<C-tab>`` : = `jedi:key-complete'
Complete code at point. (`jedi:complete')
``C-.`` : = `jedi:key-goto-definition'
Goto the definition of the object at point. (`jedi:goto-definition')
``C-c d`` : = `jedi:key-show-doc'
Goto the definition of the object at point. (`jedi:show-doc')
``C-c r`` : = `jedi:key-related-names'
Find related names of the object at point.
(`helm-jedi-related-names' / `anything-jedi-related-names')
When `jedi:setup-keys' is non-`nil', recommended keybinds are set
in `jedi-mode-map' when **loading** jedi.el. Therefore, you must
set this value before jedi.el is loaded. As recommended usage of
jedi.el is to call `jedi:setup' via `python-mode-hook' where
`jedi:setup' is autloaded, setting `jedi:setup-keys' to `t' in
you emacs setup (e.g., ``.emacs.d/init.el``) works fine.::
(setq jedi:setup-keys t)
(add-hook 'python-mode-hook 'jedi:setup)
If you want to require jedi.el explicitly when loading Emacs,
make sure to set `jedi:setup-keys' before loading jedi.el::
(setq jedi:setup-keys t)
(require 'jedi)
Byte compiler warns about unbound variable if you set
`jedi:setup-keys' before loading jedi.el. The proper way to
suppress this warning is the following::
(eval-when-compile (require 'jedi nil t))
(setq jedi:setup-keys t)
You can change these keybinds by changing `jedi:key-complete',
`jedi:key-goto-definition', `jedi:key-show-doc', and
`jedi:key-related-names'. For example, default keybind for
ropemacs's `rope-show-doc' is same as `jedi:show-doc'. You can
avoid collision by something like this::
(setq jedi:key-show-doc (kbd \"C-c D\"))"
:group 'jedi)
(defcustom jedi:key-complete (kbd "<C-tab>")
"Keybind for command `jedi:complete'."
:group 'jedi)
(defcustom jedi:key-goto-definition (kbd "C-.")
"Keybind for command `jedi:goto-definition'."
:group 'jedi)
(defcustom jedi:key-show-doc (kbd "C-c d")
"Keybind for command `jedi:show-doc'."
:group 'jedi)
(defcustom jedi:key-related-names (kbd "C-c r")
"Keybind for command `helm-jedi-related-names' or
`anything-jedi-related-names'."
:group 'jedi)
(defcustom jedi:import-python-el-settings t
"Automatically import setting from python.el variables."
:group 'jedi)
;;; Internal variables
(defvar jedi:get-in-function-call--d nil
"Bounded to deferred object while requesting get-in-function-call.")
(defvar jedi:defined-names--singleton-d nil
"Bounded to deferred object while requesting defined_names.")
;;; Jedi mode
(defvar jedi-mode-map (make-sparse-keymap))
(defun jedi:handle-post-command ()
(jedi:get-in-function-call-when-idle))
(define-minor-mode jedi-mode
"Jedi mode.
When `jedi-mode' is on, call signature is automatically shown as
toolitp when inside of function call.
\\{jedi-mode-map}"
:keymap jedi-mode-map
:group 'jedi
(let ((map jedi-mode-map))
(if jedi:complete-on-dot
(define-key map "." 'jedi:dot-complete)
(define-key map "." nil)))
(if jedi-mode
(progn
(when jedi:install-imenu
(add-hook 'after-change-functions 'jedi:after-change-handler nil t)
(jedi:defined-names-deferred)
(setq imenu-create-index-function 'jedi:create-imenu-index))
(add-hook 'post-command-hook 'jedi:handle-post-command nil t)
(add-hook 'kill-buffer-hook 'jedi:server-pool--gc-when-idle nil t))
(remove-hook 'post-command-hook 'jedi:handle-post-command t)
(remove-hook 'after-change-functions 'jedi:after-change-handler t)
(remove-hook 'kill-buffer-hook 'jedi:server-pool--gc-when-idle t)
(jedi:server-pool--gc-when-idle)))
(when jedi:setup-keys
(let ((map jedi-mode-map))
(define-key map jedi:key-complete 'jedi:complete)
(define-key map jedi:key-goto-definition 'jedi:goto-definition)
(define-key map jedi:key-show-doc 'jedi:show-doc)
(let ((command (cond
((featurep 'helm) 'helm-jedi-related-names)
((featurep 'anything) 'anything-jedi-related-names))))
(when command
(define-key map jedi:key-related-names command)))))
;;; EPC utils
(defun jedi:epc--live-p (mngr)
"Return non-nil when MNGR is an EPC manager object with a live
connection."
(let ((proc (ignore-errors
(epc:connection-process (epc:manager-connection mngr)))))
(and (processp proc)
;; Same as `process-live-p' in Emacs >= 24:
(memq (process-status proc) '(run open listen connect stop)))))
(defun jedi:epc--start-epc (server-prog server-args)
"Same as `epc:start-epc', but set query-on-exit flag for
associated processes to nil."
(let ((mngr (epc:start-epc server-prog server-args)))
(set-process-query-on-exit-flag (epc:connection-process
(epc:manager-connection mngr))
nil)
(set-process-query-on-exit-flag (epc:manager-server-process mngr) nil)
mngr))
;;; Server pool
(defvar jedi:server-pool--table (make-hash-table :test 'equal)
"A hash table that holds a pool of EPC server instances.")
(defun jedi:server-pool--start (command)
"Get an EPC server instance from server pool by COMMAND as a
key, or start new one if there is none."
(let ((cached (gethash command jedi:server-pool--table)))
(if (and cached (jedi:epc--live-p cached))
cached
(let* ((default-directory jedi:source-dir)
(mngr (jedi:epc--start-epc (car command) (cdr command))))
(puthash command mngr jedi:server-pool--table)
(jedi:server-pool--gc-when-idle)
mngr))))
(defun jedi:-get-servers-in-use ()
"Return a list of non-nil `jedi:epc' in all buffers."
(loop with mngr-list
for buffer in (buffer-list)
for mngr = (with-current-buffer buffer jedi:epc)
when (and mngr (not (memq mngr mngr-list)))
collect mngr into mngr-list
finally return mngr-list))
(defvar jedi:server-pool--gc-timer nil)
(defun jedi:server-pool--gc ()
"Stop unused servers."
(let ((servers-in-use (jedi:-get-servers-in-use)))
(maphash
(lambda (key mngr)
(unless (memq mngr servers-in-use)
(remhash key jedi:server-pool--table)
(epc:stop-epc mngr)))
jedi:server-pool--table))
;; Clear timer so that GC is started next time
;; `jedi:server-pool--gc-when-idle' is called.
(setq jedi:server-pool--gc-timer nil))
(defun jedi:server-pool--gc-when-idle ()
"Run `jedi:server-pool--gc' when idle."
(unless jedi:server-pool--gc-timer
(setq jedi:server-pool--gc-timer
(run-with-idle-timer 10 nil 'jedi:server-pool--gc))))
;;; Server management
(defun jedi:start-server ()
(if (jedi:epc--live-p jedi:epc)
(message "Jedi server is already started!")
(setq jedi:epc (jedi:server-pool--start
(append jedi:server-command jedi:server-args))))
jedi:epc)
(defun jedi:stop-server ()
"Stop Jedi server. Use this command when you want to restart
Jedi server (e.g., when you changed `jedi:server-command' or
`jedi:server-args'). Jedi srever will be restarted automatically
later when it is needed."
(interactive)
(if jedi:epc
(epc:stop-epc jedi:epc)
(message "Jedi server is already killed."))
(setq jedi:epc nil)
;; It could be non-nil due to some error. Rescue it in that case.
(setq jedi:get-in-function-call--d nil)
(setq jedi:defined-names--singleton-d nil))
(defun jedi:get-epc ()
(if (jedi:epc--live-p jedi:epc)
jedi:epc
(jedi:start-server)))
;;;###autoload
(defun jedi:start-dedicated-server (command)
"Start Jedi server dedicated to this buffer.
This is useful, for example, when you want to use different
`sys.path' for some buffer. When invoked as an interactive
command, it asks you how to start the Jedi server. You can edit
the command in minibuffer to specify the way Jedi server run.
If you want to setup how Jedi server is started programmatically
per-buffer/per-project basis, make `jedi:server-command' and
`jedi:server-args' buffer local and set it in `python-mode-hook'.
See also: `jedi:server-args'."
(interactive
(list (split-string-and-unquote
(read-string "Run Jedi server: "
(mapconcat
#'identity
(append jedi:server-command
jedi:server-args)
" ")))))
;; Reset `jedi:epc' so that a new server is created when COMMAND is
;; new. If it is already in the server pool, the server instance
;; already in the pool is picked up by `jedi:start-server'.
(setq jedi:epc nil)
;; Set `jedi:server-command', so that this command is used
;; when restarting EPC server of this buffer.
(set (make-local-variable 'jedi:server-command) command)
(set (make-local-variable 'jedi:server-args) nil)
(jedi:start-server))
(defun jedi:call-deferred (method-name)
"Call ``Script(...).METHOD-NAME`` and return a deferred object."
(let ((source (buffer-substring-no-properties (point-min) (point-max)))
(line (count-lines (point-min) (min (1+ (point)) (point-max))))
(column (current-column))
(source-path buffer-file-name))
(epc:call-deferred (jedi:get-epc)
method-name
(list source line column source-path))))
;;; Completion
(defvar jedi:complete-reply nil
"Last reply to `jedi:complete-request'.")
(defvar jedi:complete-request-point 0
;; It is passed to `=', so do not initialize this value by `nil'.
"The point where `jedi:complete-request' is called.")
(defun jedi:complete-request ()
"Request ``Script(...).complete`` and return a deferred object.
`jedi:complete-reply' is set to the reply sent from the server."
(setq jedi:complete-request-point (point))
(deferred:nextc (jedi:call-deferred 'complete)
(lambda (reply)
(setq jedi:complete-reply reply))))
;;;###autoload
(defun* jedi:complete (&key (expand ac-expand-on-auto-complete))
"Complete code at point."
(interactive)
(lexical-let ((expand expand))
(deferred:nextc (jedi:complete-request)
(lambda ()
(let ((ac-expand-on-auto-complete expand))
(auto-complete '(ac-source-jedi-direct)))))))
(defun jedi:dot-complete ()
"Insert dot and complete code at point."
(interactive)
(insert ".")
(unless (ac-cursor-on-diable-face-p)
(jedi:complete :expand nil)))
;;; AC source
(defun jedi:ac-direct-matches ()
(mapcar
(lambda (x)
(destructuring-bind (&key word doc description symbol)
x
(popup-make-item word
:symbol symbol
:document (unless (equal doc "") doc)
:summary description)))
jedi:complete-reply))
(defun jedi:ac-direct-prefix ()
(or (ac-prefix-default)
(when (= jedi:complete-request-point (point))
jedi:complete-request-point)))
;; (makunbound 'ac-source-jedi-direct)
(ac-define-source jedi-direct
'((candidates . jedi:ac-direct-matches)
(prefix . jedi:ac-direct-prefix)
(init . jedi:complete-request)
(requires . -1)))
;;;###autoload
(defun jedi:ac-setup ()
"Add Jedi AC sources to `ac-sources'."
(interactive)
(add-to-list 'ac-sources 'ac-source-jedi-direct))
;;; Call signature (get_in_function_call)
(defface jedi:highlight-function-argument
'((t (:inherit bold)))
"Face used for the argument at point in a function's argument list"
:group 'jedi)
(defun* jedi:get-in-function-call--construct-call-signature
(&key params index call_name)
(let ((current-arg (nth index params)))
(when (and current-arg (null jedi:tooltip-method))
(setf (nth index params)
(propertize current-arg 'face 'jedi:highlight-function-argument)))
(concat call_name "(" (mapconcat #'identity params ", ") ")")))
(defun jedi:get-in-function-call--tooltip-show (args)
(when (and args (not ac-completing))
(jedi:tooltip-show
(apply #'jedi:get-in-function-call--construct-call-signature args))))
(defun jedi:get-in-function-call ()
"Manually show call signature tooltip."
(interactive)
(deferred:nextc
(jedi:call-deferred 'get_in_function_call)
#'jedi:get-in-function-call--tooltip-show))
(defun jedi:get-in-function-call-when-idle ()
"Show tooltip when Emacs is ilde."
(unless jedi:get-in-function-call--d
(setq jedi:get-in-function-call--d
(deferred:try
(deferred:$
(deferred:wait-idle jedi:get-in-function-call-delay)
(deferred:nextc it
(lambda ()
(when jedi-mode ; cursor may be moved
(deferred:timeout
jedi:get-in-function-call-timeout
nil
(jedi:call-deferred 'get_in_function_call)))))
(deferred:nextc it #'jedi:get-in-function-call--tooltip-show))
:finally
(lambda ()
(setq jedi:get-in-function-call--d nil))))))
(defun jedi:tooltip-show (string)
(cond
((and (memq 'pos-tip jedi:tooltip-method) window-system
(featurep 'pos-tip))
(pos-tip-show (jedi:string-fill-paragraph string)
'popup-tip-face nil nil 0))
((and (memq 'popup jedi:tooltip-method)
(featurep 'popup))
(popup-tip string))
(t (when (stringp string)
(let ((message-log-max nil))
(message string))))))
(defun jedi:string-fill-paragraph (string &optional justify)
(with-temp-buffer
(erase-buffer)
(insert string)
(goto-char (point-min))
(fill-paragraph justify)
(buffer-string)))
;;; Goto
(defvar jedi:goto-definition--index nil)
(defvar jedi:goto-definition--cache nil)
(defun jedi:goto-definition (&optional other-window deftype use-cache index)
"Goto the definition of the object at point.
See `jedi:goto-definition-config' for how this function works
when universal prefix arguments \(``C-u``) are given. If
*numeric* prefix argument(s) \(e.g., ``M-0``) are given, goto
point of the INDEX-th result. Note that you cannot mix universal
and numeric prefixes. It is Emacs's limitation. If you mix both
kinds of prefix, you get numeric prefix.
When used as a lisp function, popup a buffer when OTHER-WINDOW is
non-nil. DEFTYPE must be either `assignment' (default) or
`definition'. When USE-CACHE is non-nil, use the locations of
the last invocation of this command. If INDEX is specified, goto
INDEX-th result."
(interactive
(if (integerp current-prefix-arg)
(list nil nil nil current-prefix-arg)
(nth (let ((i (car current-prefix-arg)))
(if i (floor (log i 4)) 0))
jedi:goto-definition-config)))
(cond
((and (or use-cache index)
jedi:goto-definition--cache)
(setq jedi:goto-definition--index (or index 0))
(jedi:goto-definition--nth other-window))
((and (eq last-command 'jedi:goto-definition)
(> (length jedi:goto-definition--cache) 1))
(jedi:goto-definition-next other-window))
(t
(setq jedi:goto-definition--index (or index 0))
(lexical-let ((other-window other-window))
(deferred:nextc (jedi:call-deferred
(case deftype
((assignment nil) 'goto)
(definition 'get_definition)
(t (error "Unsupported deftype: %s" deftype))))
(lambda (reply)
(jedi:goto-definition--callback reply other-window)))))))
(defun jedi:goto-definition-next (&optional other-window)
"Goto the next cached definition. See: `jedi:goto-definition'."
(interactive "P")
(let ((len (length jedi:goto-definition--cache))
(n (1+ jedi:goto-definition--index)))
(setq jedi:goto-definition--index (if (>= n len) 0 n))
(jedi:goto-definition--nth other-window)))
(defun jedi:goto-definition--callback (reply other-window)
(if (not reply)
(message "Definition not found.")
(setq jedi:goto-definition--cache reply)
(jedi:goto-definition--nth other-window t)))
(defun jedi:goto--line-column (line column)
"Like `goto-char' but specify the position by LINE and COLUMN."
(goto-char (point-min))
(forward-line (1- line))
(forward-char column))
(defun jedi:goto-definition--nth (other-window &optional try-next)
(let* ((len (length jedi:goto-definition--cache))
(n jedi:goto-definition--index)
(next (lambda ()
(when (< n (1- len))
(incf jedi:goto-definition--index)
(jedi:goto-definition--nth other-window)
t))))
(destructuring-bind (&key line_nr column module_path module_name
&allow-other-keys)
(nth n jedi:goto-definition--cache)
(cond
((equal module_name "__builtin__")
(unless (and try-next (funcall next))
(message "Cannot see the definition of __builtin__.")))
((not (and module_path (file-exists-p module_path)))
(unless (and try-next (funcall next))
(message "File '%s' does not exist." module_path)))
(t
(push-mark)
(funcall (if other-window #'find-file-other-window #'find-file)
module_path)
(jedi:goto--line-column line_nr column)
(jedi:goto-definition--notify-alternatives len n))))))
(defun jedi:goto-definition--notify-alternatives (len n)
(unless (= len 1)
(message
"%d-th point in %d candidates.%s"
(1+ n)
len
;; Note: It must be `last-command', not `last-command' because
;; this function is called in deferred at the first time.
(if (eq last-command 'jedi:goto-definition)
(format " Type %s to go to the next point."
(key-description
(car (where-is-internal 'jedi:goto-definition))))
""))))
;;; Full name
(defun jedi:get-full-name-deferred ()
(deferred:$
(jedi:call-deferred 'get_definition)
(deferred:nextc it
(lambda (reply)
(loop for def in reply
do (destructuring-bind (&key full_name &allow-other-keys)
def
(when full_name
(return full_name))))))))
(defun* jedi:get-full-name-sync (&key (timeout 500))
(epc:sync
(jedi:get-epc)
(deferred:timeout timeout nil (jedi:get-full-name-deferred))))
;;; Related names
(defun jedi:related-names--source (name candidates)
`((name . ,name)
(candidates . ,candidates)
(recenter)
(type . file-line)))
(defun jedi:related-names--to-file-line (reply)
(mapcar
(lambda (x)
(destructuring-bind
(&key line_nr column module_name module_path description)
x
(format "%s:%s: %s - %s" module_path line_nr
module_name description)))
reply))
(defun jedi:related-names--helm (helm)
(lexical-let ((helm helm))
(deferred:nextc
(let ((to-file-line #'jedi:related-names--to-file-line))
(deferred:parallel
(deferred:nextc (jedi:call-deferred 'related_names) to-file-line)
(deferred:nextc (jedi:call-deferred 'goto) to-file-line)))
(lambda (candidates-list)
(funcall
helm
:sources (list (jedi:related-names--source "Jedi Related Names"
(car candidates-list))
(jedi:related-names--source "Jedi Goto"
(cadr candidates-list)))
:buffer (format "*%s jedi:related-names*" helm))))))
;;;###autoload
(defun helm-jedi-related-names ()
"Find related names of the object at point using `helm' interface."
(interactive)
(jedi:related-names--helm 'helm))
;;;###autoload
(defun anything-jedi-related-names ()
"Find related names of the object at point using `anything' interface."
(interactive)
(jedi:related-names--helm 'anything))
;;; Show document (get-definition)
(defvar jedi:doc-buffer-name "*jedi:doc*")
(defun jedi:show-doc ()
"Show the documentation of the object at point."
(interactive)
(deferred:nextc (jedi:call-deferred 'get_definition)
(lambda (reply)
(with-current-buffer (get-buffer-create jedi:doc-buffer-name)
(loop with has-doc = nil
with first = t
with inhibit-read-only = t
initially (erase-buffer)
for def in reply
do (destructuring-bind
(&key doc desc_with_module line_nr module_path
&allow-other-keys)
def
(unless (or (null doc) (equal doc ""))
(if first
(setq first nil)
(insert "\n\n---\n\n"))
(insert "Docstring for " desc_with_module "\n\n" doc)
(setq has-doc t)))
finally do
(if (not has-doc)
(message "Document not found.")
(progn
(goto-char (point-min))
(when (fboundp jedi:doc-mode)
(funcall jedi:doc-mode))
(run-hooks 'jedi:doc-hook)
(funcall jedi:doc-display-buffer (current-buffer)))))))))
;;; Defined names (imenu)
(defvar jedi:defined-names--cache nil)
(make-variable-buffer-local 'jedi:defined-names--cache)
(defun jedi:defined-names-deferred ()
(deferred:nextc
(epc:call-deferred
(jedi:get-epc)
'defined_names
(list (buffer-substring-no-properties (point-min) (point-max))
buffer-file-name))
(lambda (reply)
(setq jedi:defined-names--cache reply))))
(defun jedi:defined-names--singleton-deferred ()
"Like `jedi:defined-names-deferred', but make sure that only
one request at the time is emitted."
(unless jedi:defined-names--singleton-d
(setq jedi:defined-names--singleton-d
(deferred:watch (jedi:defined-names-deferred)
(lambda (_) (setq jedi:defined-names--singleton-d nil))))))
(defun jedi:after-change-handler (&rest _)
(unless (or (ac-menu-live-p) (ac-inline-live-p))
(jedi:defined-names--singleton-deferred)))
(defun jedi:create-imenu-index-1 (def)
(destructuring-bind (&key name line_nr column &allow-other-keys) def
(cons name (save-excursion (jedi:goto--line-column line_nr column)
(point-marker)))))
(defun jedi:create-imenu-index (&optional items)
"`imenu-create-index-function' for Jedi.el.
Return an object described in `imenu--index-alist'."
(loop for (def . subdefs) in (or items jedi:defined-names--cache)
if subdefs
collect (append
(list (plist-get def :full_name)
(jedi:create-imenu-index-1 def))
(jedi:create-imenu-index subdefs))
else
collect (jedi:create-imenu-index-1 def)))
;;; Meta info
(defun jedi:get-jedi-version-request ()
"Request version of Python modules and return a deferred object."
(epc:call-deferred (jedi:get-epc) 'get_jedi_version nil))
(defun jedi:show-jedi-version ()
(interactive)
(deferred:nextc (jedi:get-jedi-version-request)
(lambda (reply)
(let ((standard-output (get-buffer-create "*jedi:version*")))
(with-current-buffer standard-output
(emacs-lisp-mode)
(erase-buffer)
(pp reply)
(display-buffer standard-output))))))
(defun jedi:print-jedi-version ()
(pp (epc:sync (jedi:get-epc) (jedi:get-jedi-version-request))))
;;; Setup
(defun jedi:import-python-el-settings-setup ()
"Make jedi aware of python.el virtualenv and path settings.
This is automatically added to the `jedi-mode-hook' when
`jedi:import-python-el-settings' is non-nil."
(let ((args))
(when (bound-and-true-p python-shell-extra-pythonpaths)
(mapc
(lambda (path)
(setq args (append (list "--sys-path" path) args)))
python-shell-extra-pythonpaths))
(when (bound-and-true-p python-shell-virtualenv-path)
(setq args
(append
(list "--virtual-env" python-shell-virtualenv-path)
args)))
(when args
(set (make-local-variable 'jedi:server-args)
(append args jedi:server-args)))))
;;;###autoload
(defun jedi:setup ()
"Fully setup jedi.el for current buffer.
It setups `ac-sources' (calls `jedi:ac-setup') and turns
`jedi-mode' on.
This function is intended to be called from `python-mode-hook',
like this::
(add-hook 'python-mode-hook 'jedi:setup)
You can also call this function as a command, to quickly test
what jedi can do."
(interactive)
(jedi:ac-setup)
(when jedi:import-python-el-settings
;; Hack to access buffer/dir-local vars: http://bit.ly/Y5IfMV.
;; Given that `jedi:setup' is added to the `python-mode-hook'
;; this will modify `hack-local-variables-hook' on python
;; buffers only and will allow us to access buffer/directory
;; local variables in `jedi:import-python-el-settings-setup'.
(add-hook 'hack-local-variables-hook
#'jedi:import-python-el-settings-setup nil t))
(jedi-mode 1))
;;; Debugging
(defvar jedi:server-command--backup nil)
(defvar jedi:server-args--backup nil)
(defun jedi:toggle-debug-server ()
"Setup `jedi:server-command' and `jedi:server-args' to debug
server using pdb or ipdb.
When this command is called, it essentially execute the following
code::
(jedi:stop-server)
(setq jedi:server-command (list \"cat\" \"jedi-port.log\" )
jedi:server-args nil)
It means to pass the port number recorded in the file
jedi-port.log to EPC client.
To start Jedi server in terminal and record port to the file,
use the following command::
python jediepcserver.py --port-file jedi-port.log --pdb
This command will be copied in the kill-ring (clipboard) when
this command is called. You can use `--ipdb` instead of `--pdb`
to use ipdb instead of pdb.
Calling this command again restores the original setting of
`jedi:server-command' and `jedi:server-args' then stops the
running server."
(interactive)
(if jedi:server-command--backup
(progn
(setq jedi:server-command jedi:server-command--backup
jedi:server-args jedi:server-args--backup)
(jedi:stop-server)
(message "Quit debugging. Original setting restored."))
(setq jedi:server-command--backup jedi:server-command
jedi:server-args--backup jedi:server-args
jedi:server-command (list "cat" (expand-file-name
"jedi-port.log" jedi:source-dir))
jedi:server-args nil)
(jedi:stop-server)
(kill-new "python jediepcserver.py --port-file jedi-port.log --ipdb")
(message "Now, start server with: --port-file jedi-port.log --ipdb.\
(command is copied in the kill-ring)")))
(provide 'jedi)
;; Local Variables:
;; coding: utf-8
;; indent-tabs-mode: nil
;; End:
;;; jedi.el ends here

View File

@ -0,0 +1,299 @@
"""
Jedi EPC server.
Copyright (C) 2012 Takafumi Arakaki
Author: Takafumi Arakaki <aka.tkf at gmail.com>
This file is NOT part of GNU Emacs.
Jedi EPC server 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.
Jedi EPC server 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 Jedi EPC server.
If not, see <http://www.gnu.org/licenses/>.
"""
import os
import sys
import re
import itertools
import logging
import site
jedi = None # I will load it later
PY3 = (sys.version_info[0] >= 3)
NEED_ENCODE = not PY3
def jedi_script(source, line, column, source_path):
if NEED_ENCODE:
source = source.encode('utf-8')
source_path = source_path and source_path.encode('utf-8')
return jedi.Script(source, line, column, source_path or '')
def candidate_symbol(comp):
"""
Return a character representing completion type.
:type comp: jedi.api.Completion
:arg comp: A completion object returned by `jedi.Script.complete`.
"""
try:
return comp.type[0].lower()
except (AttributeError, TypeError):
return '?'
def candidates_description(comp):
"""
Return `comp.description` in an appropriate format.
* Avoid return a string 'None'.
* Strip off all newlines. This is required for using
`comp.description` as candidate summary.
"""
desc = comp.description
return _WHITESPACES_RE.sub(' ', desc) if desc and desc != 'None' else ''
_WHITESPACES_RE = re.compile(r'\s+')
def complete(*args):
reply = []
for comp in jedi_script(*args).complete():
reply.append(dict(
word=comp.word,
doc=comp.doc,
description=candidates_description(comp),
symbol=candidate_symbol(comp),
))
return reply
def get_in_function_call(*args):
call_def = jedi_script(*args).get_in_function_call()
if call_def:
return dict(
# p.get_code(False) should do the job. But jedi-vim use replace.
# So follow what jedi-vim does...
params=[p.get_code().replace('\n', '') for p in call_def.params],
index=call_def.index,
call_name=call_def.call_name,
)
else:
return [] # nil
def _goto(method, *args):
"""
Helper function for `goto` and `related_names`.
:arg method: `jedi.Script.goto` or `jedi.Script.related_names`
:arg args: Arguments to `jedi_script`
"""
# `definitions` is a list. Each element is an instances of
# `jedi.api_classes.BaseOutput` subclass, i.e.,
# `jedi.api_classes.RelatedName` or `jedi.api_classes.Definition`.
definitions = method(jedi_script(*args))
return [dict(
column=d.column,
line_nr=d.line_nr,
module_path=d.module_path if d.module_path != '__builtin__' else [],
module_name=d.module_name,
description=d.description,
) for d in definitions]
def goto(*args):
return _goto(jedi.Script.goto, *args)
def related_names(*args):
return _goto(jedi.Script.related_names, *args)
def definition_to_dict(d):
return dict(
doc=d.doc,
description=d.description,
desc_with_module=d.desc_with_module,
line_nr=d.line_nr,
column=d.column,
module_path=d.module_path,
name=getattr(d, 'name', []),
full_name=getattr(d, 'full_name', []),
type=getattr(d, 'type', []),
)
def get_definition(*args):
definitions = jedi_script(*args).get_definition()
return list(map(definition_to_dict, definitions))
def get_names_recursively(definition):
"""
Fetch interesting defined names in sub-scopes under `definition`.
:type names: jedi.api_classes.Definition
"""
d = definition_to_dict(definition)
# FIXME: use appropriate method to do this (when Jedi implement some)
if definition.description.startswith('class '):
ds = definition.defined_names()
return [d] + list(map(get_names_recursively, ds))
else:
return [d]
def defined_names(*args):
return list(map(get_names_recursively, jedi.api.defined_names(*args)))
def get_module_version(module):
try:
from pkg_resources import get_distribution, DistributionNotFound
try:
return get_distribution(module.__name__).version
except DistributionNotFound:
pass
except ImportError:
pass
notfound = object()
for key in ['__version__', 'version']:
version = getattr(module, key, notfound)
if version is not notfound:
return version
def get_jedi_version():
import epc
import sexpdata
return [dict(
name=module.__name__,
file=getattr(module, '__file__', []),
version=get_module_version(module) or [],
) for module in [sys, jedi, epc, sexpdata]]
def jedi_epc_server(address='localhost', port=0, port_file=sys.stdout,
sys_path=[], virtual_env=[],
debugger=None, log=None, log_level=None):
add_virtualenv_path()
for p in virtual_env:
add_virtualenv_path(p)
sys_path = map(os.path.expandvars, map(os.path.expanduser, sys_path))
sys.path = [''] + list(filter(None, itertools.chain(sys_path, sys.path)))
# Workaround Jedi's module cache. Use this workaround until Jedi
# got an API to set module paths.
# See also: https://github.com/davidhalter/jedi/issues/36
import_jedi()
import epc.server
server = epc.server.EPCServer((address, port))
server.register_function(complete)
server.register_function(get_in_function_call)
server.register_function(goto)
server.register_function(related_names)
server.register_function(get_definition)
server.register_function(defined_names)
server.register_function(get_jedi_version)
port_file.write(str(server.server_address[1])) # needed for Emacs client
port_file.write("\n")
port_file.flush()
if port_file is not sys.stdout:
port_file.close()
if log:
server.log_traceback = True
handler = logging.FileHandler(filename=log, mode='w')
if log_level:
log_level = getattr(logging, log_level.upper())
handler.setLevel(log_level)
server.logger.setLevel(log_level)
server.logger.addHandler(handler)
if debugger:
server.set_debugger(debugger)
handler = logging.StreamHandler()
handler.setLevel(logging.DEBUG)
server.logger.addHandler(handler)
server.logger.setLevel(logging.DEBUG)
server.serve_forever()
server.logger.info('exit')
return server
def import_jedi():
global jedi
import jedi
import jedi.parsing
import jedi.evaluate
import jedi.api
return jedi
def add_virtualenv_path(venv=os.getenv('VIRTUAL_ENV')):
"""Add virtualenv's site-packages to `sys.path`."""
if not venv:
return
venv = os.path.abspath(venv)
path = os.path.join(
venv, 'lib', 'python%d.%d' % sys.version_info[:2], 'site-packages')
sys.path.insert(0, path)
site.addsitedir(path)
def main(args=None):
import argparse
parser = argparse.ArgumentParser(
formatter_class=argparse.RawTextHelpFormatter,
description=__doc__)
parser.add_argument(
'--address', default='localhost')
parser.add_argument(
'--port', default=0, type=int)
parser.add_argument(
'--port-file', '-f', default='-', type=argparse.FileType('wt'),
help='file to write port on. default is stdout.')
parser.add_argument(
'--sys-path', '-p', default=[], action='append',
help='paths to be inserted at the top of `sys.path`.')
parser.add_argument(
'--virtual-env', '-v', default=[], action='append',
help='paths to be used as if VIRTUAL_ENV is set to it.')
parser.add_argument(
'--log', help='save server log to this file.')
parser.add_argument(
'--log-level',
choices=['CRITICAL', 'ERROR', 'WARN', 'INFO', 'DEBUG'],
help='logging level for log file.')
parser.add_argument(
'--pdb', dest='debugger', const='pdb', action='store_const',
help='start pdb when error occurs.')
parser.add_argument(
'--ipdb', dest='debugger', const='ipdb', action='store_const',
help='start ipdb when error occurs.')
ns = parser.parse_args(args)
jedi_epc_server(**vars(ns))
if __name__ == '__main__':
main()

View File

@ -2,4 +2,4 @@
# name: log
# key: log
# --
console.${1:log}( $0 );
console.${1:log}($0);

254
conf/emacs.d/test-jedi.el Normal file
View File

@ -0,0 +1,254 @@
;;; test-jedi.el --- Tests for jedi.el
;; Copyright (C) 2012 Takafumi Arakaki
;; Author: Takafumi Arakaki <aka.tkf at gmail.com>
;; This file is NOT part of GNU Emacs.
;; test-jedi.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.
;; test-jedi.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 test-jedi.el.
;; If not, see <http://www.gnu.org/licenses/>.
;;; Commentary:
;;
;;; Code:
(eval-when-compile (require 'cl))
(require 'ert)
(require 'mocker)
(require 'jedi)
(defun jedi-testing:sync (d)
(epc:sync (jedi:get-epc) d))
(ert-deftest jedi:version ()
"Check if `jedi:version' can be parsed by `version-to-list'."
(version-to-list jedi:version))
;;; EPC
(ert-deftest jedi:complete-request ()
(jedi-testing:sync
(with-temp-buffer
(erase-buffer)
(insert "import json" "\n" "json.l")
(jedi:complete-request)))
(should (equal (sort (jedi:ac-direct-matches) #'string-lessp)
'("load" "loads"))))
(ert-deftest jedi:get-in-function-call-request ()
(destructuring-bind (&key params index call_name)
(jedi-testing:sync
(with-temp-buffer
(erase-buffer)
(insert "isinstance(obj,")
(jedi:call-deferred 'get_in_function_call)))
(should (equal params '("object" "class_or_type_or_tuple")))
(should (equal index 1))
(should (equal call_name "isinstance"))))
(ert-deftest jedi:goto-request ()
(let ((reply
(jedi-testing:sync
(with-temp-buffer
(erase-buffer)
(insert "import json" "\n" "json.load")
(jedi:call-deferred 'goto)))))
(destructuring-bind (&key line_nr module_path
column module_name description)
(car reply)
(should (integerp line_nr))
(should (stringp module_path)))))
(ert-deftest jedi:get-definition-request ()
(let ((reply
(jedi-testing:sync
(with-temp-buffer
(erase-buffer)
(insert "import json" "\n" "json.load")
(jedi:call-deferred 'get_definition)))))
(destructuring-bind (&key doc desc_with_module line_nr column module_path
full_name name type description)
(car reply)
(should (stringp doc))
(should (stringp desc_with_module))
(should (integerp line_nr))
(should (integerp column))
(should (stringp module_path)))))
;;; Server pool
(defmacro jedi-testing:with-mocked-server (start-epc-records
epc--live-p-records
buffers
&rest body)
(declare (indent 3))
`(let ((jedi:server-pool--table (make-hash-table :test 'equal))
(jedi:server-pool--gc-timer nil)
,@(mapcar
(lambda (b) `(,b (generate-new-buffer "*jedi test*")))
buffers))
(mocker-let
((jedi:epc--start-epc (x y) ,start-epc-records)
(jedi:epc--live-p (x) ,epc--live-p-records)
;; Probably this mocking is too "strong". What I need to
;; mock is only `buffer-list' in `jedi:-get-servers-in-use'.
(buffer-list
()
((:input nil
:output-generator
(lambda ()
(loop for b in (list ,@buffers)
when (buffer-live-p b) collect b))
:min-occur 0)))
(jedi:server-pool--gc-when-idle
()
((:record-cls 'mocker-stub-record))))
(macrolet ((check-restart (&rest args)
`(jedi-testing:check-start-server ,@args))
(set-server
(command &optional args)
`(progn
(set (make-local-variable 'jedi:server-command) ,command)
(set (make-local-variable 'jedi:server-args) ,args))))
(unwind-protect
(progn ,@body)
(mapc #'kill-buffer (list ,@buffers)))))))
(defun jedi-testing:check-start-server (buffer command server)
(with-current-buffer buffer
(should-not jedi:epc)
(should (eq (let ((jedi:server-command command)
(jedi:server-args nil))
(jedi:start-server))
server))
(should (eq jedi:epc server))))
(ert-deftest jedi:pool-single-server ()
"Successive call of `jedi:start-server' with the same setup should
return the same server instance."
(jedi-testing:with-mocked-server
;; Mock `epc:start-epc':
((:input '("python" ("jediepcserver.py")) :output 'dummy-server))
;; Mock `jedi:epc--live-p':
((:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-server) :output t)) ; via `jedi:server-pool--start'
;; Buffers to use:
(buf1 buf2)
(check-restart buf1 '("python" "jediepcserver.py") 'dummy-server)
(check-restart buf2 '("python" "jediepcserver.py") 'dummy-server)))
(ert-deftest jedi:pool-per-buffer-server ()
"Successive call of `jedi:start-server' with different setups should
return the different server instances."
(jedi-testing:with-mocked-server
;; Mock `epc:start-epc':
((:input '("python" ("jediepcserver.py")) :output 'dummy-server-1)
(:input '("python3" ("jediepcserver.py")) :output 'dummy-server-2))
;; Mock `jedi:epc--live-p':
((:input '(nil) :output nil)) ; via `jedi:start-server'
;; Buffers to use:
(buf1 buf2)
(check-restart buf1 '("python" "jediepcserver.py") 'dummy-server-1)
(check-restart buf2 '("python3" "jediepcserver.py") 'dummy-server-2)))
(ert-deftest jedi:pool-restart-per-buffer-server ()
"When one of the server died, only the died server must be
rebooted; not still living ones."
(jedi-testing:with-mocked-server
;; Mock `epc:start-epc':
((:input '("python" ("jediepcserver.py")) :output 'dummy-server-1)
(:input '("python3" ("jediepcserver.py")) :output 'dummy-server-2)
(:input '("python" ("jediepcserver.py")) :output 'dummy-server-3))
;; Mock `jedi:epc--live-p':
((:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-server-1) :output t)
(:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-server-1) :output nil) ; server is stopped
(:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-server-2) :output t)
(:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-server-3) :output t))
;; Buffers to use:
(buf1 buf2 buf3)
(check-restart buf1 '("python" "jediepcserver.py") 'dummy-server-1)
(check-restart buf2 '("python3" "jediepcserver.py") 'dummy-server-2)
(check-restart buf3 '("python" "jediepcserver.py") 'dummy-server-1)
(mapc (lambda (b) (with-current-buffer b (setq jedi:epc nil)))
(list buf1 buf2 buf3))
;; Now, ``(jedi:epc--live-p dummy-server-1)`` will return nil:
(check-restart buf1 '("python" "jediepcserver.py") 'dummy-server-3)
(check-restart buf2 '("python3" "jediepcserver.py") 'dummy-server-2)
(check-restart buf3 '("python" "jediepcserver.py") 'dummy-server-3)))
(ert-deftest jedi:pool-buffer-local-server-setting ()
"Locally set `jedi:server-command' and `jedi:server-args' must be used."
(jedi-testing:with-mocked-server
;; Mock `epc:start-epc':
((:input '("server" ("-abc")) :output 'dummy-1)
(:input '("server" ("-xyz")) :output 'dummy-2))
;; Mock `jedi:epc--live-p':
((:input '(nil) :output nil) ; via `jedi:start-server'
(:input '(dummy-1) :output t)) ; via `jedi:server-pool--start'
;; Buffers to use:
(buf1 buf2 buf3)
;; Set buffer local `jedi:server-command':
(with-current-buffer buf1 (set-server '("server" "-abc")))
(with-current-buffer buf2 (set-server '("server" "-xyz")))
(with-current-buffer buf3 (set-server '("server" "-abc")))
;; Check that the buffer local `jedi:server-command' is used:
(should (eq (with-current-buffer buf1 (jedi:start-server)) 'dummy-1))
(should (eq (with-current-buffer buf2 (jedi:start-server)) 'dummy-2))
(should (eq (with-current-buffer buf3 (jedi:start-server)) 'dummy-1))))
(ert-deftest jedi:pool-gc-when-no-jedi-buffers ()
"GC should stop servers when there is no Jedi buffers."
(jedi-testing:with-mocked-server
;; Mock `epc:start-epc':
((:input '("server" ("-abc")) :output 'dummy-1)
(:input '("server" ("-xyz")) :output 'dummy-2))
;; Mock `jedi:epc--live-p':
((:input '(nil) :output nil)) ; via `jedi:start-server'
;; Buffers to use:
(buf1 buf2)
;; Check that in this mocked environment there is no server yet:
(should (= (length (jedi:-get-servers-in-use)) 0))
;; Start servers:
(check-restart buf1 '("server" "-abc") 'dummy-1)
(check-restart buf2 '("server" "-xyz") 'dummy-2)
;; GC should not stop servers in use:
(jedi:server-pool--gc)
(should (= (length (jedi:-get-servers-in-use)) 2))
;; GC should stop unused servers:
(mapc #'kill-buffer (list buf1 buf2))
(mocker-let ((epc:stop-epc (x) ((:input '(dummy-1))
(:input '(dummy-2)))))
(jedi:server-pool--gc))
(should (= (length (jedi:-get-servers-in-use)) 0))))
(provide 'test-jedi)
;; Local Variables:
;; coding: utf-8
;; indent-tabs-mode: nil
;; End:
;;; test-jedi.el ends here

View File

@ -0,0 +1,27 @@
import os
from contextlib import contextmanager
from jediepcserver import add_virtualenv_path
@contextmanager
def osenv(*args, **kwds):
def putenvs(dct):
for (k, v) in dct.items():
if v is None:
del os.environ[k]
else:
os.environ[k] = v
newenv = dict(*args, **kwds)
oldenv = dict(zip(newenv, map(os.getenv, newenv)))
try:
putenvs(newenv)
yield
finally:
putenvs(oldenv)
def test_add_virtualenv_path_runs_fine_in_non_virtualenv():
# See: https://github.com/tkf/emacs-jedi/issues/3
with osenv(VIRTUAL_ENV=None):
add_virtualenv_path()

View File

@ -0,0 +1,6 @@
(require 'auto-complete)
(setq jedi:setup-keys t)
(require 'jedi)
(global-auto-complete-mode +1)
(add-hook 'python-mode-hook 'jedi:setup)
(setq jedi:complete-on-dot t)