##// END OF EJS Templates
Merge pull request #1932 from takluyver/i1916...
Thomas Kluyver -
r7521:b6137d28 merge
parent child Browse files
Show More
@@ -1,229 +1,230 b''
1 """hooks for IPython.
1 """hooks for IPython.
2
2
3 In Python, it is possible to overwrite any method of any object if you really
3 In Python, it is possible to overwrite any method of any object if you really
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
4 want to. But IPython exposes a few 'hooks', methods which are _designed_ to
5 be overwritten by users for customization purposes. This module defines the
5 be overwritten by users for customization purposes. This module defines the
6 default versions of all such hooks, which get used by IPython if not
6 default versions of all such hooks, which get used by IPython if not
7 overridden by the user.
7 overridden by the user.
8
8
9 hooks are simple functions, but they should be declared with 'self' as their
9 hooks are simple functions, but they should be declared with 'self' as their
10 first argument, because when activated they are registered into IPython as
10 first argument, because when activated they are registered into IPython as
11 instance methods. The self argument will be the IPython running instance
11 instance methods. The self argument will be the IPython running instance
12 itself, so hooks have full access to the entire IPython object.
12 itself, so hooks have full access to the entire IPython object.
13
13
14 If you wish to define a new hook and activate it, you need to put the
14 If you wish to define a new hook and activate it, you need to put the
15 necessary code into a python file which can be either imported or execfile()'d
15 necessary code into a python file which can be either imported or execfile()'d
16 from within your profile's ipython_config.py configuration.
16 from within your profile's ipython_config.py configuration.
17
17
18 For example, suppose that you have a module called 'myiphooks' in your
18 For example, suppose that you have a module called 'myiphooks' in your
19 PYTHONPATH, which contains the following definition:
19 PYTHONPATH, which contains the following definition:
20
20
21 import os
21 import os
22 from IPython.core import ipapi
22 from IPython.core import ipapi
23 ip = ipapi.get()
23 ip = ipapi.get()
24
24
25 def calljed(self,filename, linenum):
25 def calljed(self,filename, linenum):
26 "My editor hook calls the jed editor directly."
26 "My editor hook calls the jed editor directly."
27 print "Calling my own editor, jed ..."
27 print "Calling my own editor, jed ..."
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
28 if os.system('jed +%d %s' % (linenum,filename)) != 0:
29 raise TryNext()
29 raise TryNext()
30
30
31 ip.set_hook('editor', calljed)
31 ip.set_hook('editor', calljed)
32
32
33 You can then enable the functionality by doing 'import myiphooks'
33 You can then enable the functionality by doing 'import myiphooks'
34 somewhere in your configuration files or ipython command line.
34 somewhere in your configuration files or ipython command line.
35 """
35 """
36
36
37 #*****************************************************************************
37 #*****************************************************************************
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
38 # Copyright (C) 2005 Fernando Perez. <fperez@colorado.edu>
39 #
39 #
40 # Distributed under the terms of the BSD License. The full license is in
40 # Distributed under the terms of the BSD License. The full license is in
41 # the file COPYING, distributed as part of this software.
41 # the file COPYING, distributed as part of this software.
42 #*****************************************************************************
42 #*****************************************************************************
43
43
44 import os, bisect
44 import os
45 import subprocess
45 import subprocess
46 import sys
46 import sys
47
47
48 from IPython.core.error import TryNext
48 from IPython.core.error import TryNext
49
49
50 # List here all the default hooks. For now it's just the editor functions
50 # List here all the default hooks. For now it's just the editor functions
51 # but over time we'll move here all the public API for user-accessible things.
51 # but over time we'll move here all the public API for user-accessible things.
52
52
53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
53 __all__ = ['editor', 'fix_error_editor', 'synchronize_with_editor',
54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
54 'input_prefilter', 'shutdown_hook', 'late_startup_hook',
55 'show_in_pager','pre_prompt_hook',
55 'show_in_pager','pre_prompt_hook',
56 'pre_run_code_hook', 'clipboard_get']
56 'pre_run_code_hook', 'clipboard_get']
57
57
58 def editor(self, filename, linenum=None, wait=True):
58 def editor(self, filename, linenum=None, wait=True):
59 """Open the default editor at the given filename and linenumber.
59 """Open the default editor at the given filename and linenumber.
60
60
61 This is IPython's default editor hook, you can use it as an example to
61 This is IPython's default editor hook, you can use it as an example to
62 write your own modified one. To set your own editor function as the
62 write your own modified one. To set your own editor function as the
63 new editor hook, call ip.set_hook('editor',yourfunc)."""
63 new editor hook, call ip.set_hook('editor',yourfunc)."""
64
64
65 # IPython configures a default editor at startup by reading $EDITOR from
65 # IPython configures a default editor at startup by reading $EDITOR from
66 # the environment, and falling back on vi (unix) or notepad (win32).
66 # the environment, and falling back on vi (unix) or notepad (win32).
67 editor = self.editor
67 editor = self.editor
68
68
69 # marker for at which line to open the file (for existing objects)
69 # marker for at which line to open the file (for existing objects)
70 if linenum is None or editor=='notepad':
70 if linenum is None or editor=='notepad':
71 linemark = ''
71 linemark = ''
72 else:
72 else:
73 linemark = '+%d' % int(linenum)
73 linemark = '+%d' % int(linenum)
74
74
75 # Enclose in quotes if necessary and legal
75 # Enclose in quotes if necessary and legal
76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
76 if ' ' in editor and os.path.isfile(editor) and editor[0] != '"':
77 editor = '"%s"' % editor
77 editor = '"%s"' % editor
78
78
79 # Call the actual editor
79 # Call the actual editor
80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
80 proc = subprocess.Popen('%s %s %s' % (editor, linemark, filename),
81 shell=True)
81 shell=True)
82 if wait and proc.wait() != 0:
82 if wait and proc.wait() != 0:
83 raise TryNext()
83 raise TryNext()
84
84
85 import tempfile
85 import tempfile
86 def fix_error_editor(self,filename,linenum,column,msg):
86 def fix_error_editor(self,filename,linenum,column,msg):
87 """Open the editor at the given filename, linenumber, column and
87 """Open the editor at the given filename, linenumber, column and
88 show an error message. This is used for correcting syntax errors.
88 show an error message. This is used for correcting syntax errors.
89 The current implementation only has special support for the VIM editor,
89 The current implementation only has special support for the VIM editor,
90 and falls back on the 'editor' hook if VIM is not used.
90 and falls back on the 'editor' hook if VIM is not used.
91
91
92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
92 Call ip.set_hook('fix_error_editor',youfunc) to use your own function,
93 """
93 """
94 def vim_quickfix_file():
94 def vim_quickfix_file():
95 t = tempfile.NamedTemporaryFile()
95 t = tempfile.NamedTemporaryFile()
96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
96 t.write('%s:%d:%d:%s\n' % (filename,linenum,column,msg))
97 t.flush()
97 t.flush()
98 return t
98 return t
99 if os.path.basename(self.editor) != 'vim':
99 if os.path.basename(self.editor) != 'vim':
100 self.hooks.editor(filename,linenum)
100 self.hooks.editor(filename,linenum)
101 return
101 return
102 t = vim_quickfix_file()
102 t = vim_quickfix_file()
103 try:
103 try:
104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
104 if os.system('vim --cmd "set errorformat=%f:%l:%c:%m" -q ' + t.name):
105 raise TryNext()
105 raise TryNext()
106 finally:
106 finally:
107 t.close()
107 t.close()
108
108
109
109
110 def synchronize_with_editor(self, filename, linenum, column):
110 def synchronize_with_editor(self, filename, linenum, column):
111 pass
111 pass
112
112
113
113
114 class CommandChainDispatcher:
114 class CommandChainDispatcher:
115 """ Dispatch calls to a chain of commands until some func can handle it
115 """ Dispatch calls to a chain of commands until some func can handle it
116
116
117 Usage: instantiate, execute "add" to add commands (with optional
117 Usage: instantiate, execute "add" to add commands (with optional
118 priority), execute normally via f() calling mechanism.
118 priority), execute normally via f() calling mechanism.
119
119
120 """
120 """
121 def __init__(self,commands=None):
121 def __init__(self,commands=None):
122 if commands is None:
122 if commands is None:
123 self.chain = []
123 self.chain = []
124 else:
124 else:
125 self.chain = commands
125 self.chain = commands
126
126
127
127
128 def __call__(self,*args, **kw):
128 def __call__(self,*args, **kw):
129 """ Command chain is called just like normal func.
129 """ Command chain is called just like normal func.
130
130
131 This will call all funcs in chain with the same args as were given to
131 This will call all funcs in chain with the same args as were given to
132 this function, and return the result of first func that didn't raise
132 this function, and return the result of first func that didn't raise
133 TryNext"""
133 TryNext"""
134 last_exc = TryNext()
134 last_exc = TryNext()
135 for prio,cmd in self.chain:
135 for prio,cmd in self.chain:
136 #print "prio",prio,"cmd",cmd #dbg
136 #print "prio",prio,"cmd",cmd #dbg
137 try:
137 try:
138 return cmd(*args, **kw)
138 return cmd(*args, **kw)
139 except TryNext as exc:
139 except TryNext as exc:
140 last_exc = exc
140 last_exc = exc
141 # if no function will accept it, raise TryNext up to the caller
141 # if no function will accept it, raise TryNext up to the caller
142 raise last_exc
142 raise last_exc
143
143
144 def __str__(self):
144 def __str__(self):
145 return str(self.chain)
145 return str(self.chain)
146
146
147 def add(self, func, priority=0):
147 def add(self, func, priority=0):
148 """ Add a func to the cmd chain with given priority """
148 """ Add a func to the cmd chain with given priority """
149 bisect.insort(self.chain,(priority,func))
149 self.chain.append((priority, func))
150 self.chain.sort(key=lambda x: x[0])
150
151
151 def __iter__(self):
152 def __iter__(self):
152 """ Return all objects in chain.
153 """ Return all objects in chain.
153
154
154 Handy if the objects are not callable.
155 Handy if the objects are not callable.
155 """
156 """
156 return iter(self.chain)
157 return iter(self.chain)
157
158
158
159
159 def input_prefilter(self,line):
160 def input_prefilter(self,line):
160 """ Default input prefilter
161 """ Default input prefilter
161
162
162 This returns the line as unchanged, so that the interpreter
163 This returns the line as unchanged, so that the interpreter
163 knows that nothing was done and proceeds with "classic" prefiltering
164 knows that nothing was done and proceeds with "classic" prefiltering
164 (%magics, !shell commands etc.).
165 (%magics, !shell commands etc.).
165
166
166 Note that leading whitespace is not passed to this hook. Prefilter
167 Note that leading whitespace is not passed to this hook. Prefilter
167 can't alter indentation.
168 can't alter indentation.
168
169
169 """
170 """
170 #print "attempt to rewrite",line #dbg
171 #print "attempt to rewrite",line #dbg
171 return line
172 return line
172
173
173
174
174 def shutdown_hook(self):
175 def shutdown_hook(self):
175 """ default shutdown hook
176 """ default shutdown hook
176
177
177 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
178 Typically, shotdown hooks should raise TryNext so all shutdown ops are done
178 """
179 """
179
180
180 #print "default shutdown hook ok" # dbg
181 #print "default shutdown hook ok" # dbg
181 return
182 return
182
183
183
184
184 def late_startup_hook(self):
185 def late_startup_hook(self):
185 """ Executed after ipython has been constructed and configured
186 """ Executed after ipython has been constructed and configured
186
187
187 """
188 """
188 #print "default startup hook ok" # dbg
189 #print "default startup hook ok" # dbg
189
190
190
191
191 def show_in_pager(self,s):
192 def show_in_pager(self,s):
192 """ Run a string through pager """
193 """ Run a string through pager """
193 # raising TryNext here will use the default paging functionality
194 # raising TryNext here will use the default paging functionality
194 raise TryNext
195 raise TryNext
195
196
196
197
197 def pre_prompt_hook(self):
198 def pre_prompt_hook(self):
198 """ Run before displaying the next prompt
199 """ Run before displaying the next prompt
199
200
200 Use this e.g. to display output from asynchronous operations (in order
201 Use this e.g. to display output from asynchronous operations (in order
201 to not mess up text entry)
202 to not mess up text entry)
202 """
203 """
203
204
204 return None
205 return None
205
206
206
207
207 def pre_run_code_hook(self):
208 def pre_run_code_hook(self):
208 """ Executed before running the (prefiltered) code in IPython """
209 """ Executed before running the (prefiltered) code in IPython """
209 return None
210 return None
210
211
211
212
212 def clipboard_get(self):
213 def clipboard_get(self):
213 """ Get text from the clipboard.
214 """ Get text from the clipboard.
214 """
215 """
215 from IPython.lib.clipboard import (
216 from IPython.lib.clipboard import (
216 osx_clipboard_get, tkinter_clipboard_get,
217 osx_clipboard_get, tkinter_clipboard_get,
217 win32_clipboard_get
218 win32_clipboard_get
218 )
219 )
219 if sys.platform == 'win32':
220 if sys.platform == 'win32':
220 chain = [win32_clipboard_get, tkinter_clipboard_get]
221 chain = [win32_clipboard_get, tkinter_clipboard_get]
221 elif sys.platform == 'darwin':
222 elif sys.platform == 'darwin':
222 chain = [osx_clipboard_get, tkinter_clipboard_get]
223 chain = [osx_clipboard_get, tkinter_clipboard_get]
223 else:
224 else:
224 chain = [tkinter_clipboard_get]
225 chain = [tkinter_clipboard_get]
225 dispatcher = CommandChainDispatcher()
226 dispatcher = CommandChainDispatcher()
226 for func in chain:
227 for func in chain:
227 dispatcher.add(func)
228 dispatcher.add(func)
228 text = dispatcher()
229 text = dispatcher()
229 return text
230 return text
@@ -1,75 +1,81 b''
1 # -*- coding: utf-8 -*-
1 # -*- coding: utf-8 -*-
2 """Tests for CommandChainDispatcher."""
2 """Tests for CommandChainDispatcher."""
3
3
4 from __future__ import absolute_import
4 from __future__ import absolute_import
5
5
6 #-----------------------------------------------------------------------------
6 #-----------------------------------------------------------------------------
7 # Imports
7 # Imports
8 #-----------------------------------------------------------------------------
8 #-----------------------------------------------------------------------------
9
9
10 import nose.tools as nt
10 import nose.tools as nt
11 from IPython.core.error import TryNext
11 from IPython.core.error import TryNext
12 from IPython.core.hooks import CommandChainDispatcher
12 from IPython.core.hooks import CommandChainDispatcher
13
13
14 #-----------------------------------------------------------------------------
14 #-----------------------------------------------------------------------------
15 # Local utilities
15 # Local utilities
16 #-----------------------------------------------------------------------------
16 #-----------------------------------------------------------------------------
17
17
18 # Define two classes, one which succeeds and one which raises TryNext. Each
18 # Define two classes, one which succeeds and one which raises TryNext. Each
19 # sets the attribute `called` to True when it is called.
19 # sets the attribute `called` to True when it is called.
20 class Okay(object):
20 class Okay(object):
21 def __init__(self, message):
21 def __init__(self, message):
22 self.message = message
22 self.message = message
23 self.called = False
23 self.called = False
24 def __call__(self):
24 def __call__(self):
25 self.called = True
25 self.called = True
26 return self.message
26 return self.message
27
27
28 class Fail(object):
28 class Fail(object):
29 def __init__(self, message):
29 def __init__(self, message):
30 self.message = message
30 self.message = message
31 self.called = False
31 self.called = False
32 def __call__(self):
32 def __call__(self):
33 self.called = True
33 self.called = True
34 raise TryNext(self.message)
34 raise TryNext(self.message)
35
35
36 #-----------------------------------------------------------------------------
36 #-----------------------------------------------------------------------------
37 # Test functions
37 # Test functions
38 #-----------------------------------------------------------------------------
38 #-----------------------------------------------------------------------------
39
39
40 def test_command_chain_dispatcher_ff():
40 def test_command_chain_dispatcher_ff():
41 """Test two failing hooks"""
41 """Test two failing hooks"""
42 fail1 = Fail(u'fail1')
42 fail1 = Fail(u'fail1')
43 fail2 = Fail(u'fail2')
43 fail2 = Fail(u'fail2')
44 dp = CommandChainDispatcher([(0, fail1),
44 dp = CommandChainDispatcher([(0, fail1),
45 (10, fail2)])
45 (10, fail2)])
46
46
47 try:
47 try:
48 dp()
48 dp()
49 except TryNext as e:
49 except TryNext as e:
50 nt.assert_equal(str(e), u'fail2')
50 nt.assert_equal(str(e), u'fail2')
51 else:
51 else:
52 assert False, "Expected exception was not raised."
52 assert False, "Expected exception was not raised."
53
53
54 nt.assert_true(fail1.called)
54 nt.assert_true(fail1.called)
55 nt.assert_true(fail2.called)
55 nt.assert_true(fail2.called)
56
56
57 def test_command_chain_dispatcher_fofo():
57 def test_command_chain_dispatcher_fofo():
58 """Test a mixture of failing and succeeding hooks."""
58 """Test a mixture of failing and succeeding hooks."""
59 fail1 = Fail(u'fail1')
59 fail1 = Fail(u'fail1')
60 fail2 = Fail(u'fail2')
60 fail2 = Fail(u'fail2')
61 okay1 = Okay(u'okay1')
61 okay1 = Okay(u'okay1')
62 okay2 = Okay(u'okay2')
62 okay2 = Okay(u'okay2')
63
63
64 dp = CommandChainDispatcher([(0, fail1),
64 dp = CommandChainDispatcher([(0, fail1),
65 # (5, okay1), # add this later
65 # (5, okay1), # add this later
66 (10, fail2),
66 (10, fail2),
67 (15, okay2)])
67 (15, okay2)])
68 dp.add(okay1, 5)
68 dp.add(okay1, 5)
69
69
70 nt.assert_equal(dp(), u'okay1')
70 nt.assert_equal(dp(), u'okay1')
71
71
72 nt.assert_true(fail1.called)
72 nt.assert_true(fail1.called)
73 nt.assert_true(okay1.called)
73 nt.assert_true(okay1.called)
74 nt.assert_false(fail2.called)
74 nt.assert_false(fail2.called)
75 nt.assert_false(okay2.called)
75 nt.assert_false(okay2.called)
76
77 def test_command_chain_dispatcher_eq_priority():
78 okay1 = Okay(u'okay1')
79 okay2 = Okay(u'okay2')
80 dp = CommandChainDispatcher([(1, okay1)])
81 dp.add(okay2, 1)
General Comments 0
You need to be logged in to leave comments. Login now