##// END OF EJS Templates
Merge pull request #1955 from ivanov/vim-ipython...
Min RK -
r7568:69f93d47 merge
parent child Browse files
Show More
@@ -16,7 +16,7 b' IPython.'
16 16
17 17 The big change from previous versions of ``ipy.vim`` is that it no longer
18 18 requires the old brittle ``ipy_vimserver.py`` instantiation, and since
19 it uses just vim and python, it is platform independent (i.e. should work
19 it uses just vim and python, it is platform independent (i.e. works
20 20 even on windows, unlike the previous \*nix only solution). The requirements
21 21 are IPython 0.11+ with zeromq capabilities, vim compiled with +python.
22 22
@@ -45,15 +45,28 b' and for IPython 0.12, like this::'
45 45 :IPython --existing kernel-85997.json
46 46
47 47 The ``:IPythonClipboard`` command just uses the ``+`` register to get the
48 connection string, whereas ``:IPythonXSelection`` uses the ``*`` register
48 connection string, whereas ``:IPythonXSelection`` uses the ``*`` register.
49
50 **NEW in IPython 0.12**!
51 Since IPython 0.12, you can simply use::
52
53 :IPython
54
55 without arguments to connect to the most recent IPython session (this is the
56 same as passing just the ``--existing`` flag to ``ipython qtconsole`` and
57 ``ipython console``.
49 58
50 59 .. [*] Though the demos above use ``qtconsole``, it is not required
51 60 for this workflow, it's just that it was the easiest way to show how to
52 make use of the new functionality in 0.11 release. In the current git
53 trunk of IPython, you can use ``ipython kernel`` to create a kernel and
54 get the connection string to use for any frontend (including vim-ipython).
55 If you are still using 0.11, you can launch a regular kernel using
56 ``python -c "from IPython.zmq.ipkernel import main; main()"``
61 make use of the new functionality in 0.11 release. Since IPython 0.12, you
62 can use ``ipython kernel`` to create a kernel and get the connection
63 string to use for any frontend (including vim-ipython), or use ``ipython
64 console`` to create a kernel and immediately connect to it using a
65 terminal-based client. You can even connect to an active IPython Notebook
66 kernel - just watch for the connection string that gets printed when you
67 open the notebook, or use the ``%connect_info`` magic to get the
68 connection string. If you are still using 0.11, you can launch a regular
69 kernel using ``python -c "from IPython.zmq.ipkernel import main; main()"``
57 70
58 71 ------------------------
59 72 Sending lines to IPython
@@ -87,12 +100,17 b' Then, go to the qtconsole and run this line::'
87 100
88 101 You can also send whole files to IPython's ``%run`` magic using ``<F5>``.
89 102
103 **NEW in IPython 0.12**!
104 If you're trying to do run code fragments that have leading whitespace, use
105 ``<Alt-S>`` instead - it will dedent a single line, and remove the leading
106 whitespace of the first line from all lines in a visual mode selection.
107
90 108 -------------------------------
91 109 IPython's object? Functionality
92 110 -------------------------------
93 111
94 If you're using gvim, mouse-over a variable to see IPython's ? equivalent. If
95 you're using vim from a terminal, or want to copy something from the
112 If you're using gvim, mouse-over a variable to see IPython's ``?`` equivalent.
113 If you're using vim from a terminal, or want to copy something from the
96 114 docstring, type ``<leader>d``. ``<leader>`` is usually ``\`` (the backslash
97 115 key). This will open a quickpreview window, which can be closed by hitting
98 116 ``q`` or ``<escape>``.
@@ -108,7 +126,6 b' completion.'
108 126 -------------------
109 127 vim-ipython 'shell'
110 128 -------------------
111 **NEW since IPython 0.11**!
112 129
113 130 By monitoring km.sub_channel, we can recreate what messages were sent to
114 131 IPython, and what IPython sends back in response.
@@ -119,6 +136,11 b' updated on every sent command (default: True).'
119 136 If at any later time you wish to bring this shell up, including if you've set
120 137 ``monitor_subchannel=False``, hit ``<leader>s``.
121 138
139 **NEW since IPython 0.12**
140 For local kernels (kernels running on the same machine as vim), `Ctrl-C` in
141 the vim-ipython 'shell' sends an keyboard interrupt. (Note: this feature may
142 not work on Windows, please report the issue to ).
143
122 144 -------
123 145 Options
124 146 -------
@@ -134,6 +156,14 b" In your own ``.vimrc``, if you don't like the mappings provided by default,"
134 156 you can define a variable ``let g:ipy_perform_mappings=0`` which will prevent
135 157 vim-ipython from defining any of the default mappings.
136 158
159 **NEW since IPython 0.12**
160 **Making completefunc local to a buffer, or disabling it**
161 By default, vim-ipython activates the custom completefunc globally.
162 Sometimes, having a completefunc breaks other plugins' completions. Putting
163 the line ``let g:ipy_completefunc = 'local'`` in one's vimrc will activate the
164 IPython-based completion only for current buffer. Setting `g:ipy_completefunc`
165 to anything other than `'local'` or `'global'` disables it altogether.
166
137 167 ---------------
138 168 Current issues:
139 169 ---------------
@@ -161,7 +191,7 b' Current issues:'
161 191 if there's a difference between ::
162 192
163 193 $ vim -c ':py import os; print os.__file__' -c ':q'
164 $ python -c ':py import os; print os.__file__'
194 $ python -c 'import os; print os.__file__'
165 195
166 196 - For vim inside a terminal, using the arrow keys won't work inside a
167 197 documentation buffer, because the mapping for ``<Esc>`` overlaps with
@@ -179,11 +209,45 b' Current issues:'
179 209 ----------------------------
180 210 Thanks and Bug Participation
181 211 ----------------------------
182 * @MinRK for guiding me through the IPython kernel manager protocol.
183 * @nakamuray and @tcheneau for reporting and providing a fix for when vim is compiled without a gui (#1)
184 * @unpingco for reporting Windows bugs (#3,#4)
212 Here's a brief acknowledgment of the folks who have graciously pitched in. If
213 you've been missed, don't hesitate to contact me, or better yet, submit a
214 pull request with your attribution.
215
216 * @minrk for guiding me through the IPython kernel manager protocol, and
217 support of connection_file-based IPython connection (#13)
218 * @nakamuray and @tcheneau for reporting and providing a fix for when vim is
219 compiled without a gui (#1)
220 * @unpingco for reporting Windows bugs (#3,#4), providing better multiline
221 dedenting (#15), and suggesting that a resized vim-ipython shell stays
222 resized (#16).
185 223 * @simon-b for terminal vim arrow key issue (#5)
186 * @jorgesca and @kwgoodman for shell (#6)
224 * @jorgesca and @kwgoodman for shell update problems (#6)
225 * @xowlinx and @vladimiroff for Ctrl-S issues in Konsole (#8)
187 226 * @zeekay for easily allowing custom mappings (#9)
188 * @minrk for support of connection_file-based IPython connection (#13)
189 * @jorgesca for reporting the lack of profile handling capability (#14)
227 * @jorgesca for reporting the lack of profile handling capability (#14),
228 only open updating 'shell' if it is open (#29)
229 * @enzbang for removing mapping that's not currently functional (#17)
230 * @ogrisel for fixing documentation typo (#19)
231 * @koepsell for gracefully exiting in case python is not available (#23)
232 * @mrterry for activating completefunc only after a connection is made (#25),
233 Ctrl-C implementation in vim-ipython 'shell' (#28)
234 * @nonameentername for completion on import statements (#26)
235 * @dstahlke for setting syntax of doc window to ReST
236 * @jtratner for docs with quotes (#30)
237 * @pielgrzym for setting completefunc locally to a buffer (#32)
238
239 Similar Projects
240 ----------------
241 * `vim-slime`_ - Grab some text and "send" it to a GNU Screen / tmux session
242 (Jonathan Palardy)
243 * `screen.vba`_ - Simulate a split shell, using GNU Screen / tmux, that you
244 can send commands to (Eric Van Dewoestine)
245 * conque_ - terminal emulator which uses a Vim buffer to display the program
246 output (Nico Raffo)
247 * `ipyqtmacvim`_ - plugin to send commands from MacVim to IPython Qt console
248 (Justin Kitzes)
249
250 .. _vim-slime: https://github.com/jpalardy/vim-slime
251 .. _screen.vba: https://github.com/ervandew/screen
252 .. _conque: http://code.google.com/p/conque/
253 .. _ipyqtmacvim: https://github.com/jkitzes/ipyqtmacvim/
@@ -1,6 +1,6 b''
1 1 " Vim integration with IPython 0.11+
2 2 "
3 " A two-way integration between Vim and IPython.
3 " A two-way integration between Vim and IPython.
4 4 "
5 5 " Using this plugin, you can send lines or whole files for IPython to execute,
6 6 " and also get back object introspection and word completions in Vim, like
@@ -11,16 +11,40 b''
11 11 " -----------------
12 12 " Start ipython qtconsole and copy the connection string.
13 13 " Source this file, which provides new IPython command
14 " :source ipy.vim
15 " :IPythonClipboard
14 " :source ipy.vim
15 " :IPythonClipboard
16 16 " (or :IPythonXSelection if you're using X11 without having to copy)
17 17 "
18 18 " written by Paul Ivanov (http://pirsquared.org)
19 "
20 if !has('python')
21 " exit if python is not available.
22 finish
23 endif
24
25 " Allow custom mappings.
26 if !exists('g:ipy_perform_mappings')
27 let g:ipy_perform_mappings = 1
28 endif
29
30 " Register IPython completefunc
31 " 'global' -- for all of vim (default).
32 " 'local' -- only for the current buffer.
33 " otherwise -- don't register it at all.
34 "
35 " you can later set it using ':set completefunc=CompleteIPython', which will
36 " correspond to the 'global' behavior, or with ':setl ...' to get the 'local'
37 " behavior
38 if !exists('g:ipy_completefunc')
39 let g:ipy_completefunc = 'global'
40 endif
41
19 42 python << EOF
20 43 reselect = False # reselect lines after sending from Visual mode
21 44 show_execution_count = True # wait to get numbers for In[43]: feedback?
22 45 monitor_subchannel = True # update vim-ipython 'shell' on every send?
23 46 run_flags= "-i" # flags to for IPython's run magic when using <F5>
47 current_line = ''
24 48
25 49 import vim
26 50 import sys
@@ -48,6 +72,10 b' try:'
48 72 km
49 73 except NameError:
50 74 km = None
75 try:
76 pid
77 except NameError:
78 pid = None
51 79
52 80 def km_from_string(s=''):
53 81 """create kernel manager from IPKernelApp string
@@ -103,6 +131,26 b" def km_from_string(s=''):"
103 131 return
104 132 km.start_channels()
105 133 send = km.shell_channel.execute
134
135 # now that we're connect to an ipython kernel, activate completion
136 # machinery, but do so only for the local buffer if the user added the
137 # following line the vimrc:
138 # let g:ipy_completefunc = 'local'
139 vim.command("""
140 if g:ipy_completefunc == 'global'
141 set completefunc=CompleteIPython
142 elseif g:ipy_completefunc == 'local'
143 setl completefunc=CompleteIPython
144 endif
145 """)
146 # also activate GUI doc balloons if in gvim
147 vim.command("""
148 if has('balloon_eval')
149 set bexpr=IPythonBalloonExpr()
150 set ballooneval
151 endif
152 """)
153 set_pid()
106 154 return km
107 155
108 156 def echo(arg,style="Question"):
@@ -120,7 +168,7 b' def disconnect():'
120 168
121 169 def get_doc(word):
122 170 if km is None:
123 return ["Not connected to IPython, cannot query \"%s\"" %word]
171 return ["Not connected to IPython, cannot query: %s" % word]
124 172 msg_id = km.shell_channel.object_info(word)
125 173 doc = get_doc_msg(msg_id)
126 174 # get around unicode problems when interfacing with vim
@@ -131,7 +179,7 b' import re'
131 179 strip = re.compile('\x1B\[([0-9]{1,2}(;[0-9]{1,2})?)?[m|K]')
132 180 def strip_color_escapes(s):
133 181 return strip.sub('',s)
134
182
135 183 def get_doc_msg(msg_id):
136 184 n = 13 # longest field name (empirically)
137 185 b=[]
@@ -164,13 +212,11 b' def get_doc_buffer(level=0):'
164 212 word = vim.eval('expand("<cfile>")') or ''
165 213 doc = get_doc(word)
166 214 if len(doc) ==0:
167 echo(word+" not found","Error")
215 echo(repr(word)+" not found","Error")
168 216 return
169 # close any currently open preview windows
170 vim.command('pcl')
171 217 # documentation buffer name is same as the query made to ipython
172 218 vim.command('new '+word)
173 vim.command('setlocal pvw modifiable noro')
219 vim.command('setlocal modifiable noro')
174 220 # doc window quick quit keys: 'q' and 'escape'
175 221 vim.command('map <buffer> q :q<CR>')
176 222 # Known issue: to enable the use of arrow keys inside the terminal when
@@ -188,8 +234,27 b' def get_doc_buffer(level=0):'
188 234 #vim.command('pcl')
189 235 #vim.command('pedit doc')
190 236 #vim.command('normal ') # go to previous window
237 # use the ReST formatting that ships with stock vim
238 vim.command('setlocal syntax=rst')
239
240 def vim_ipython_is_open():
241 """
242 Helper function to let us know if the vim-ipython shell is currently
243 visible
244 """
245 for w in vim.windows:
246 if w.buffer.name is not None and w.buffer.name.endswith("vim-ipython"):
247 return True
248 return False
191 249
192 def update_subchannel_msgs(debug=False):
250 def update_subchannel_msgs(debug=False, force=False):
251 """
252 Grab any pending messages and place them inside the vim-ipython shell.
253 This function will do nothing if the vim-ipython shell is not visible,
254 unless force=True argument is passed.
255 """
256 if km is None or (not vim_ipython_is_open() and not force):
257 return False
193 258 msgs = km.sub_channel.get_msgs()
194 259 if debug:
195 260 #try:
@@ -223,6 +288,15 b' def update_subchannel_msgs(debug=False):'
223 288 # subchannel window quick quit key 'q'
224 289 vim.command('map <buffer> q :q<CR>')
225 290 vim.command("set bufhidden=hide buftype=nofile ft=python")
291 # make shift-enter and control-enter in insert mode behave same as in ipython notebook
292 # shift-enter send the current line, control-enter send the line
293 # but keeps it around for further editing.
294 vim.command("imap <buffer> <s-Enter> <esc>dd:python run_command('''<C-r>\"''')<CR>i")
295 # pkddA: paste, go up one line which is blank after run_command,
296 # delete it, and then back to insert mode
297 vim.command("imap <buffer> <c-Enter> <esc>dd:python run_command('''<C-r>\"''')<CR>pkddA")
298 # ctrl-C gets sent to the IPython process as a signal on POSIX
299 vim.command("map <buffer>  :IPythonInterrupt<cr>")
226 300
227 301 #syntax highlighting for python prompt
228 302 # QtConsole In[] is blue, but I prefer the oldschool green
@@ -234,6 +308,7 b' def update_subchannel_msgs(debug=False):'
234 308 vim.command("syn match Green /^In \[[0-9]*\]\:/")
235 309 vim.command("syn match Red /^Out\[[0-9]*\]\:/")
236 310 b = vim.current.buffer
311 update_occured = False
237 312 for m in msgs:
238 313 #db.append(str(m).splitlines())
239 314 s = ''
@@ -274,12 +349,14 b' def update_subchannel_msgs(debug=False):'
274 349 b.append(s.splitlines())
275 350 except:
276 351 b.append([l.encode(vim_encoding) for l in s.splitlines()])
352 update_occured = True
277 353 # make a newline so we can just start typing there
278 354 if b[-1] != '':
279 355 b.append([''])
280 356 vim.command('normal G') # go to the end of the file
281 357 if not startedin_vimipython:
282 358 vim.command('normal p') # go back to where you were
359 return update_occured
283 360
284 361 def get_child_msg(msg_id):
285 362 # XXX: message handling should be split into its own process in the future
@@ -292,7 +369,7 b' def get_child_msg(msg_id):'
292 369 #got a message, but not the one we were looking for
293 370 echo('skipping a message on shell_channel','WarningMsg')
294 371 return m
295
372
296 373 def print_prompt(prompt,msg_id=None):
297 374 """Print In[] or In[42] style messages"""
298 375 global show_execution_count
@@ -332,8 +409,6 b' def run_this_line():'
332 409 def run_command(cmd):
333 410 msg_id = send(cmd)
334 411 print_prompt(cmd, msg_id)
335 if monitor_subchannel:
336 update_subchannel_msgs()
337 412
338 413 @with_subchannel
339 414 def run_these_lines():
@@ -354,6 +429,51 b' def run_these_lines():'
354 429 prompt = "lines %d-%d "% (r.start+1,r.end+1)
355 430 print_prompt(prompt,msg_id)
356 431
432
433 def set_pid():
434 """
435 Explicitly ask the ipython kernel for its pid
436 """
437 global km, pid
438 lines = '\n'.join(['import os', '_pid = os.getpid()'])
439 msg_id = send(lines, silent=True, user_variables=['_pid'])
440
441 # wait to get message back from kernel
442 try:
443 child = get_child_msg(msg_id)
444 except Empty:
445 echo("no reply from IPython kernel")
446 return
447
448 pid = int(child['content']['user_variables']['_pid'])
449 return pid
450
451
452 def interrupt_kernel_hack():
453 """
454 Sends the interrupt signal to the remote kernel. This side steps the
455 (non-functional) ipython interrupt mechanisms.
456 Only works on posix.
457 """
458 global pid
459 import signal
460 import os
461 if pid is None:
462 # Avoid errors if we couldn't get pid originally,
463 # by trying to obtain it now
464 pid = set_pid()
465
466 if pid is None:
467 echo("cannot get kernel PID, Ctrl-C will not be supported")
468 return
469 echo("KeyboardInterrupt (sent to ipython: pid " +
470 "%i with signal %i)" % (pid, signal.SIGINT),"Operator")
471 try:
472 os.kill(pid, signal.SIGINT)
473 except OSError:
474 echo("unable to kill pid %d" % pid)
475 pid = None
476
357 477 def dedent_run_this_line():
358 478 vim.command("left")
359 479 run_this_line()
@@ -366,7 +486,7 b' def dedent_run_these_lines():'
366 486 vim.command("'<,'>" + "<"*count)
367 487 run_these_lines()
368 488 vim.command("silent undo")
369
489
370 490 #def set_this_line():
371 491 # # not sure if there's a way to do this, since we have multiple clients
372 492 # send("get_ipython().shell.set_next_input(\'%s\')" % vim.current.line.replace("\'","\\\'"))
@@ -382,9 +502,9 b' def toggle_reselect():'
382 502 #def set_breakpoint():
383 503 # send("__IP.InteractiveTB.pdb.set_break('%s',%d)" % (vim.current.buffer.name,
384 504 # vim.current.window.cursor[0]))
385 # print "set breakpoint in %s:%d"% (vim.current.buffer.name,
505 # print "set breakpoint in %s:%d"% (vim.current.buffer.name,
386 506 # vim.current.window.cursor[0])
387 #
507 #
388 508 #def clear_breakpoint():
389 509 # send("__IP.InteractiveTB.pdb.clear_break('%s',%d)" % (vim.current.buffer.name,
390 510 # vim.current.window.cursor[0]))
@@ -414,16 +534,41 b' fun! <SID>toggle_send_on_save()'
414 534 endif
415 535 endfun
416 536
417 " Allow custom mappings
418 if !exists('g:ipy_perform_mappings')
419 let g:ipy_perform_mappings = 1
420 endif
537 " Update the vim-ipython shell when the cursor is not moving.
538 " You can change how quickly this happens after you stop moving the cursor by
539 " setting 'updatetime' (in milliseconds). For example, to have this event
540 " trigger after 1 second:
541 "
542 " :set updatetime 1000
543 "
544 " NOTE: This will only be triggered once, after the first 'updatetime'
545 " milliseconds, *not* every 'updatetime' milliseconds. see :help CursorHold
546 " for more info.
547 "
548 " TODO: Make this easily configurable on the fly, so that an introspection
549 " buffer we may have opened up doesn't get closed just because of an idle
550 " event (i.e. user pressed \d and then left the buffer that popped up, but
551 " expects it to stay there).
552 au CursorHold *.*,vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on idle)",'Operator')
553
554 " XXX: broken - cursor hold update for insert mode moves the cursor one
555 " character to the left of the last character (update_subchannel_msgs must be
556 " doing this)
557 "au CursorHoldI *.* :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on idle)",'Operator')
558
559 " Same as above, but on regaining window focus (mostly for GUIs)
560 au FocusGained *.*,vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on input focus)",'Operator')
561
562 " Update vim-ipython buffer when we move the cursor there. A message is only
563 " displayed if vim-ipython buffer has been updated.
564 au BufEnter vim-ipython :python if update_subchannel_msgs(): echo("vim-ipython shell updated (on buffer enter)",'Operator')
565
421 566 if g:ipy_perform_mappings != 0
422 567 map <silent> <F5> :python run_this_file()<CR>
423 568 map <silent> <S-F5> :python run_this_line()<CR>
424 569 map <silent> <F9> :python run_these_lines()<CR>
425 570 map <silent> <leader>d :py get_doc_buffer()<CR>
426 map <silent> <leader>s :py update_subchannel_msgs(); echo("vim-ipython shell updated",'Operator')<CR>
571 map <silent> <leader>s :py if update_subchannel_msgs(force=True): echo("vim-ipython shell updated",'Operator')<CR>
427 572 map <silent> <S-F9> :python toggle_reselect()<CR>
428 573 "map <silent> <C-F6> :python send('%pdb')<CR>
429 574 "map <silent> <F6> :python set_breakpoint()<CR>
@@ -435,9 +580,9 b' if g:ipy_perform_mappings != 0'
435 580 imap <silent> <F5> <C-O><F5>
436 581 map <C-F5> :call <SID>toggle_send_on_save()<CR>
437 582 "" Example of how to quickly clear the current plot with a keystroke
438 map <silent> <F12> :python run_command("plt.clf()")<cr>
583 "map <silent> <F12> :python run_command("plt.clf()")<cr>
439 584 "" Example of how to quickly close all figures with a keystroke
440 map <silent> <F11> :python run_command("plt.close('all')")<cr>
585 "map <silent> <F11> :python run_command("plt.close('all')")<cr>
441 586
442 587 "pi custom
443 588 map <silent> <C-Return> :python run_this_file()<CR>
@@ -455,6 +600,7 b' endif'
455 600 command! -nargs=* IPython :py km_from_string("<args>")
456 601 command! -nargs=0 IPythonClipboard :py km_from_string(vim.eval('@+'))
457 602 command! -nargs=0 IPythonXSelection :py km_from_string(vim.eval('@*'))
603 command! -nargs=0 IPythonInterrupt :py interrupt_kernel_hack()
458 604
459 605 function! IPythonBalloonExpr()
460 606 python << endpython
@@ -464,28 +610,27 b' vim.command("let l:doc = %s"% reply)'
464 610 endpython
465 611 return l:doc
466 612 endfunction
467 if has('balloon_eval')
468 set bexpr=IPythonBalloonExpr()
469 set ballooneval
470 endif
471 613
472 614 fun! CompleteIPython(findstart, base)
473 if a:findstart
474 " locate the start of the word
475 let line = getline('.')
476 let start = col('.') - 1
477 while start > 0 && line[start-1] =~ '\k\|\.' "keyword
478 let start -= 1
479 endwhile
615 if a:findstart
616 " locate the start of the word
617 let line = getline('.')
618 let start = col('.') - 1
619 while start > 0 && line[start-1] =~ '\k\|\.' "keyword
620 let start -= 1
621 endwhile
480 622 echo start
481 return start
482 else
483 " find months matching with "a:base"
484 let res = []
623 python << endpython
624 current_line = vim.current.line
625 endpython
626 return start
627 else
628 " find months matching with "a:base"
629 let res = []
485 630 python << endpython
486 631 base = vim.eval("a:base")
487 632 findstart = vim.eval("a:findstart")
488 msg_id = km.shell_channel.complete(base, vim.current.line, vim.eval("col('.')"))
633 msg_id = km.shell_channel.complete(base, current_line, vim.eval("col('.')"))
489 634 try:
490 635 m = get_child_msg(msg_id)
491 636 matches = m['content']['matches']
@@ -515,7 +660,6 b' for c in completions:'
515 660 vim.command('call add(res,"'+c+'")')
516 661 endpython
517 662 "call extend(res,completions)
518 return res
519 endif
520 endfun
521 set completefunc=CompleteIPython
663 return res
664 endif
665 endfun
General Comments 0
You need to be logged in to leave comments. Login now