Commits

Tamas Kovacs  committed 401225b

Version 0.2.0: Major update: Lisp REPL inside a Vim buffer

  • Participants
  • Parent commits 37cb969

Comments (0)

Files changed (3)

File doc/slimv.txt

-*slimv.txt*                    Slimv                 Last Change: 21 Feb 2009
+*slimv.txt*                    Slimv                 Last Change: 28 Feb 2009
 
 Slimv                                                                  *slimv*
-                               Version 0.1.4
+                               Version 0.2.0
 
 The Superior Lisp Interaction Mode for Vim.
 This plugin is aimed to help Lisp development by interfacing between Vim and
 |slimv-installation|         Installation
 |slimv-customization|        Customization
 |slimv-usage|                Usage
-|slimv-functions|            Function Reference
+|slimv-repl|                 Lisp REPL inside Vim
 |slimv-faq|                  Frequently Asked Questions
 |slimv-changelog|            Change Log
 |slimv-issues|               Known Issues
 
 {Vi does not have any of this}
 
-==============================================================================
-INSTALLATION                                              *slimv-installation*
+===============================================================================
+INSTALLATION                                               *slimv-installation*
 
 Prerequisites:
   - Lisp (any Common Lisp implementation should be OK) installed.
   - Python 2.4 or later installed.
+  - The interrupt functionality needs the pywin32 extension on Windows.
 
 To install the script:
   - Download the slimv.zip.
 -------------------------------------------------------------------------------
                                                                 *slimv_options*
 
-Note: Most options below require to restart the Vim session when modified.
+The list below contains an alphabetical collection of Slimv options.
+Below that list follows the detailed explanation on each option.
+
+|g:slimv_client|             The whole OS command to start the Slimv client.
+                             Used for advanced customization, like changing
+                             the terminal emulator to use for the Lisp REPL.
+
+|g:slimv_debug|              Debug log level. Set zero for no debug messages.
+
+|g:slimv_debug_client|       Display the command to start client.
+
+|g:slimv_keybindings|        Predefined Slimv keybindings. Possible values:
+                             1 = set #1, 2 = set #2, other = no keybindings
+
+|g:slimv_lisp|               Path for the Lisp interpreter.
+
+|g:slimv_logfile|            Name of the logfile to use for debug messages.
+                             Also see |g:slimv_debug|.
+
+|g:slimv_menu|               If nonzero, Slimv menu is added to the Vim menu
+
+|g:slimv_port|               TCP/IP port number to use in the Slimv client-
+                             server communication.
+
+|g:slimv_python|             Path for the Python interpreter.
+
+|g:slimv_repl_dir|           Directory path for the Lisp REPL buffer file.
+
+|g:slimv_repl_file|          Filename without path for the REPL buffer file.
+
+|g:slimv_repl_open|          If nonzero, Slimv opens the Lisp REPL buffer
+                             inside Vim when the server is started.
+
+|g:slimv_repl_split|         Open the Lisp REPL buffer in a split window
+                             or in a separate buffer.
+
+|g:slimv_repl_wait|          Number of seconds to wait for the REPL output
+                             to finish. Zero means inifinity.
+
+
+Note: Most options require to restart the Vim session when modified.
 
 Slimv tries to autodetect the Python and Lisp installation directories,
 however the algorithm is not very sophisticated.
     let g:slimv_port = 10101
 
                                                                *g:slimv_client*
-You may want to use a shell frontend other then the default one or put
-additional command line parameters for starting the server.
+You may want to use a shell frontend other then the default one (cmd.exe
+on Windows, xterm on Linux) or put additional command line parameters
+for starting the server.
 In this case you can omit the variables above and use g:slimv_client instead.
 This is the complete command that is used to start the client. If not set
 by the user, this is automatically built from the variables above.
 
-The command format is the following:
-    <python> <slimv> -p <port> -r <server_cmd> -f <file_to_REPL>
+The command format is the following (items in [] are optional):
+    <python> <slimv> [-p <port>] -r "<server_cmd>"
 Where:
     <python>       is the command to start the Python interpreter
     <slimv>        is the path of Slimv.py
     <port>         is the port number to use (if other than default)
     <server_cmd>   is the command to start the server
-    <file_to_REPL> is the file containing Lisp forms to be sent to the REPL
 
-The format of <server_cmd> is the following:
-    <python> <slimv> -l <lisp> -s
+The format of <server_cmd> is the following (please remember to enclose the
+whole command in double quotes, as this will be passed as one parameter
+to the client):
+    [<terminal>] <python> <slimv> -l <lisp> -s
 Where:
+    <terminal>     is the command to open a new terminal window
     <python>       is the command to start the Python interpreter
     <slimv>        is the path of Slimv.py
     <lisp>         is the command to start the Lisp REPL
 You can also pass the following shortcuts in <server_cmd>:
     @p             equals <python>
     @s             equals <slimv>
-    @l             equals <lisp>
     @@             use it if you need to insert an @ character
     \"             use it to insert a " character
+So <server_cmd> can be rewritten in this shorter form:
+    [<terminal>] @p @s -l <lisp> -s
 
 The reason behind the duplication of the <python> and <slimv> part is that
 you may want to start the server/REPL in a shell frontend that needs
       inside a command line argument, otherwise the argument will be split
       by the shell.
 
-Example to start the Slimv server via Console:
+Example to start the Slimv server via Console on Windows:
 
   let g:slimv_client = 
   \ 'python slimv.py -p 5152 -r "console -r \"/k @p @s -l clisp -s\""'
 
   console -r "/k python slimv.py -l clisp -s"
 
+A similar situation is if you want to use a terminal program other than
+xterm on Linux (e.g. Konsole). A typical g:slimv_client setup can be on
+Linux for xterm:
+
+  let g:slimv_client =
+  \ 'python ~/.vim/plugin/slimv.py -r "xterm -T Slimv -e @p @s -l clisp -s"'
+
+And this can be for Konsole:
+
+  let g:slimv_client =
+  \ 'python ~/.vim/plugin/slimv.py -r "konsole -T Slimv -e @p @s -l clisp -s"'
+
                                                                 *g:slimv_debug*
 Defines the debug log level. Level 0 means no debug messages at all. The debug
 messages go into the logfile defined by |slimv-logfile|.
 
                                                          *g:slimv_debug_client*
-Set this to nonzero only if you want to keep the Slimv client window open.
-Currently works only on Windows.
+Set this to nonzero if you want to make Vim display the command used to start
+the client and any occurrent error messages. This also makes Slimv keep the
+Slimv client window open on Windows. This setting is useful to debug problems
+when setting up a custom |g:slimv_client| command.
 
                                                               *g:slimv_logfile*
 Name of the Slimv logfile. Defaults to slimv.log.
+Used only when |g:slimv_debug| level is nonzero.
 
                                                           *g:slimv_keybindings*
 Defines the keybinding set used by Slimv. Value 0 means no keybinding at all.
 Value 1 defines the short keybinding with one-key bindings (after <Leader>).
 Value 2 defines the easy keybinding with two-key bindings (after <Leader>).
 Other values mean no predefined keybinding is wanted.
+The <Leader> is set to ',' by default. 
 
                                                                  *g:slimv_menu*
-If nonzero then the Slimv menu us added to the end of the global menu.
+If nonzero then the Slimv menu is added to the end of the global menu.
 Also the Slimv menu can be shown by pressing <Leader>, (defaults to ,,).
 
 
+                                                             *g:slimv_repl_dir*
+Directory path for the Lisp REPL buffer file. By default this is a directory
+for temporary files, something like /tmp/ on Linux or
+"C:\Documents and Settings\Username\Local Settings\Temp\" on Windows.
+The directory name must end with the pathname separator (/ or \).
+See also |g:slimv_repl_file|.
+
+                                                            *g:slimv_repl_file*
+The Lisp REPL output is written to a temporary file by the Slimv server.
+|g:slimv_repl_file| defines the filename part of the REPL output file without
+the directory path. The complete REPL filename is built from
+|g:slimv_repl_dir| and |g:slimv_repl_file|.
+
+                                                            *g:slimv_repl_open*
+Slimv opens the Lisp REPL buffer inside Vim by default when the Slimv server
+is started, so there exist actually two REPL-s with the same contents:
+one inside a Vim buffer and another one as a separate terminal window.
+The reason behind this is that the simulation is not perfect, which is caused
+by the lack of asynchronous update possibilities in Vim. Sometimes the REPL
+buffer is not perfectly updated, this is the case for example when a Lisp
+program is running too long and it has infrequent outputs only.
+In these situations the REPL buffer can be refreshed manually or the
+separate REPL window can be used for checking the output.
+The |g:slimv_repl_open| = 0 option can be used to disable the built-in REPL
+buffer, so that only the separate REPL window is opened.
+
+                                                           *g:slimv_repl_split*
+Open the Lisp REPL buffer in a split window or in a separate buffer in Vim.
+Used only when |g:slimv_repl_open| is nonzero.
+
+                                                            *g:slimv_repl_wait*
+Number of seconds to wait for the REPL output to finish. Zero means inifinity.
+While waiting, the output is displayed continuously. During that time the
+regular Vim commands are not available. For the posssible commands see
+|slimv-repl|.
+
+
 -------------------------------------------------------------------------------
                                                               *slimv_templates*
 
 
     ,,  Slimv Menu
 
-    ,S  Connect to Server
-
     ,d  Eval Defun
     ,e  Eval Last Expression
     ,E  Pprint Eval Last Expression
     ,s  Describe Symbol
     ,a  Apropos
 
+    ,S  Connect to Server
+    ,z  Refresh REPL Buffer
+    ,Z  Refresh REPL Now
+
 There is another easy to remember built in keybinding set
 (g:slimv_keybindings=2):
  
     ,,  Slimv Menu
 
-    Connection commands:
-    ,cs  Connect to Server
-
     Evaluation commands:
     ,ed  Eval Defun
     ,ee  Eval Last Expression
     ,ds  Describe Symbol
     ,da  Apropos
 
+    REPL commands:
+    ,rc  Connect to Server
+    ,rr  Refresh REPL Buffer
+    ,rn  Refresh REPL Now
 
-==============================================================================
-USAGE                                                            *slimv-usage*
+
+===============================================================================
+USAGE                                                             *slimv-usage*
 
 After proper installation start Vim and load a *.lisp source file into a
 buffer. When the first Slimv command is entered (either from the menu or
 
 
 ===============================================================================
+LISP REPL                                                          *slimv-repl*
+
+The Lisp REPL is displayed as a separate terminal window and also inside a
+Vim buffer. The Lisp REPL buffer is opened when the Slimv server is started.
+The REPL buffer is a more or less regular Vim buffer, all Vim keybindings and
+commands can be used here as well.
+
+There are however some subtle differences. The main idea is that the last line
+in the REPL buffer is a "command line", just like in any REPL. The command
+line usually begins with a prompt, something like "[1] > ". The user types the
+command after the prompt in Insert mode. When Enter (<CR>) is pressed, the
+contents of the command line (which can actually be multiple lines, when
+pasted) is sent to the Lisp REPL for evaluation. It is not allowed to
+backspace before the end of the command line prompt.
+Please remember that this evaluation style is working only in Insert mode,
+in Normal mode the function of <CR> is left unchanged.
+Other areas of the REPL buffer can be used to eval Lisp forms, just like
+from the .lisp source code. So it is possible to move the cursor inside a form
+that was previously eval-ed, make some changes, then eval it again.
+Please note, that after evaluation the REPL buffer is refreshed, so the
+changes made to the form are restored at that location, but the changed form
+will be evaluated at the end of the REPL buffer.
+
+Another difference is the command line history, which can be activated by
+pressing <Up> or <Down> in the command line (also only in Insert mode).
+
+The keys with modified meanings in the Lisp REPL buffer are listed below:
+
+Insert Mode:
+
+    <CR>           Sends the command typed in the last line to the Lisp REPL
+                   for evaluation.
+
+    <BS>           In the last line it deletes characters to the left only
+                   until the end of the Lisp prompt reached.
+
+    <Up>           Brings up the previous command typed and sent to the Lisp
+                   REPL.
+
+    <Down>         Brings up the next command typed and sent to the Lisp REPL.
+
+    <C-X><C-X>     Sends a Ctrl+C (SIGINT) signal to the Lisp REPL (see below).
+
+Normal Mode:
+
+    <C-X>          Sends a Ctrl+C (SIGINT) signal to the Lisp REPL (see below).
+
+While waiting for REPL output:
+
+    <C-X>          Sends a Ctrl+C (SIGINT) signal to the Lisp REPL. Useful for
+                   interrupting a runaway Lisp program from the Lisp REPL
+                   buffer. <C-C> key combination does not seem to work inside
+                   Vim, that is why <C-X> has been choosen.
+
+    <ESC>          Stops waiting for REPL output, returns to regular Vim mode.
+                   It is possible to return to the continuous refresh mode
+                   by selecting the "Refresh REPL Buffer" command.
+
+
+===============================================================================
 FAQ                                                                 *slimv-faq*
 
 - Q: Why is this plugin called 'Slimv'?
      then I found an (already abandoned) project called 'Slim-Vim' and I did
      not want to interfere with it.
 
-- Q: Why another 'Superior Lisp Mode' if there is already one?
+- Q: Why another 'Superior Lisp Mode' if there is already one (for Emacs)?
 - A: Because many programmers prefer Vim as a program text editor over Emacs,
      including me. I don't want to start a holy war or whatsoever, I'm just
      happy if someone else finds this plugin useful.
 
+- Q: But there are other similar projects for Vim. Why having yet another
+     SLIMxxx for Vim?
+- A: To my knowledge, none of the above mentioned Vim scripts/extensions
+     contain all the functionalities of SLIME (nor does Slimv, to tell the
+     truth). There is room for improvement, so let's keep on working, and
+     then we'll see, which Vim plugin gets the closest to SLIME.
+     In my opinion the main barrier is the lack of asynchronous buffer update
+     in Vim, but this may change in the future.
+
 - Q: How does Slimv work?
 - A: Slimv consists of three parts: Vim plugin, client and server.
      The Slimv server is a swank server that embeds a Lisp REPL.
      So the dataflow is like this:
      Vim -> Vim plugin -> Slimv client -> Slimv server -> Lisp REPL
      The plugin resides in 'slimv.vim', the client and the server both
-     reside in 'slimv.py'.
+     located in 'slimv.py'.
 
 - Q: Why is SLIME functionality XYZ missing from Slimv?
 - A: There are two possible reasons:
 ===============================================================================
 CHANGE LOG                                                    *slimv-changelog*
 
+0.2.0  - Major update: Lisp REPL displayed in a Vim buffer.
+
 0.1.4  - Corrected the delayed display of last line in REPL window on Linux.
        - Ctrl+C is propagated to Lisp REPL, so it is possible to interrupt
          a running program. Does not work however with some Lisp
 0.1    - Initial release.
 
 ===============================================================================
-KNOWN ISSUES                                                     *slimv-issues*
+ISSUES, LIMITATIONS, KNOWN BUGS                                  *slimv-issues*
 
 - Needs Vim version 7.0 or above, because of the intensive use of lists.
 - Vim register 's' is used for all form selections, so its original content is
   destroyed.
+- Vim mark 's' is used to mark the end of the REPL buffer, i.e. the beginning
+  of the "command line".
 - (un)profile does not work
 - Needs Python 2.4 or higher (uses the subprocess module)
 - Works only via the Python interpreter, does not work using a Python IDE
   (like IDLE).
+- Sometimes a Python exception happens after a Ctrl-C followed by an EOF
+  (Ctrl-Z or Ctrl-D).
+- REPL buffer refresh on Vim focus gain works only in gvim, not in console vim.
+- REPL buffer refresh sometimes skipped first time after the server Lisp window
+  was just opened.
+- Interruption (Ctrl+C or Ctrl+X) does not work with all Lisp implementations.
+  For example SBCL exits on Windows when receiving Ctrl+C.
 
 ===============================================================================
 TODO                                                               *slimv-todo*
 
-- Rewrite client/server in Lisp, so that no Python would be needed.
-- Add Compile System
-- Add Cross Reference functions
-- Add Profile functions
-- Handle specific REPL output in Vim (like compilation notes)
-- Use a Vim buffer for the REPL. This does not look possible with the current
-  state of Vim, or at least I can not see any easy way to do it.
+- Make the script a lisp filetype plugin.
+- Start client and server separately having two simpler commands for client
+  and server startup, instead of the existing g:slimv_client solution.
+- Implement an own main loop in Vim for the REPL buffer, poll REPL output
+  regularly, handle all Vim keypresses and commands inside the main loop,
+  continuously update the REPL buffer.
+- Rewrite client/server in Lisp, so that no Python would be needed,
+- Or put the (Python) client part back into the .vim script, rewrite only the
+  server part in Lisp.
+- Handle specific REPL output in Vim (like compilation notes).
+- Add Compile System.
+- Add Cross Reference functions.
+- Add Profile functions.
 
 ===============================================================================
 CREDITS                                                         *slimv-credits*

File plugin/slimv.py

 #
 # Client/Server code for Slimv
 # slimv.py:     Client/Server code for slimv.vim plugin
-# Version:      0.1.4
-# Last Change:  21 Feb 2009
+# Version:      0.2.0
+# Last Change:  28 Feb 2009
 # Maintainer:   Tamas Kovacs <kovisoft at gmail dot com>
 # License:      This file is placed in the public domain.
 #               No warranty, express or implied.
     if run_cmd == '':
         # Complex run command not given, build it from the information available
         if mswindows:
-            cmd = [python_path, slimv_path, '-p', str(PORT), '-l', lisp_path, '-s']
+            cmd = []
         else:
-            cmd = ['xterm', '-T', 'Slimv', '-e', python_path, slimv_path, '-p', str(PORT), '-l', lisp_path, '-s']
+            cmd = ['xterm', '-T', 'Slimv', '-e']
+        cmd = cmd + [python_path, slimv_path, '-p', str(PORT), '-l', lisp_path, '-s']
     else:
         cmd = shlex.split(run_cmd)
 
 ###############################################################################
 
 class repl_buffer:
-    def __init__ ( self ):
+    def __init__ ( self, output_pipe ):
 
-        self.buffer = ''    # Text buffer (display queue) to collect socket input and REPL output
-        self.buflen = 0     # Amount of text currently in the buffer
-        self.sema   = BoundedSemaphore()
+        self.output   = output_pipe
+        self.filename = ''
+        self.buffer   = ''
+        self.sema     = BoundedSemaphore()
                             # Semaphore to synchronize access to the global display queue
 
-    def read_and_display( self, output ):
-        """Read and display lines received in global display queue buffer.
-        """
+    def setfile( self, filename ):
         self.sema.acquire()
-        l = len( self.buffer )
-        while self.buflen < l:
-            try:
-                # Write all lines in the buffer to the display
-                #output.write( self.buffer[self.buflen] )
-                os.write( output.fileno(), self.buffer[self.buflen] )
-                self.buflen = self.buflen + 1
-            except:
-                break
-        self.buffer = ''
-        self.buflen = 0
+        self.filename = filename
         self.sema.release()
 
-    def write( self, text ):
+    def write( self, text, fileonly=False ):
         """Write text into the global display queue buffer.
         """
         self.sema.acquire()
-        self.buffer = self.buffer + text
+        if not fileonly:
+            try:
+                # Write all lines to the display
+                os.write( self.output.fileno(), text )
+            except:
+                pass
+
+        if self.filename != '':
+            tries = 4
+            while tries > 0:
+                try:
+                    file = open( self.filename, 'at' )
+                    try:
+                        #file.write( text )
+                        if self.buffer != '':
+                            # There is output pending
+                            os.write(file.fileno(), self.buffer )
+                            self.buffer = ''
+                        os.write(file.fileno(), text )
+                    finally:
+                        file.close()
+                    tries = 0
+                except:
+                    tries = tries - 1
+        elif len( self.buffer ) < 2000:
+            # No filename supplied, collect output info a buffer until filename is given
+            # We collect only some bytes, then probably no filename will be given at all
+            self.buffer = self.buffer + text
         self.sema.release()
 
 
     """Server thread to receive text from the client via socket.
     """
 
-    def __init__ ( self, inp, buffer ):
+    def __init__ ( self, inp, buffer, pid ):
         Thread.__init__( self )
         self.inp = inp
         self.buffer = buffer
+        self.pid = pid
 
     def run( self ):
         global terminate
                     except:
                         break
 
-                    # Fork here: write message to the stdin of REPL
-                    # and also write it to the display (display queue buffer)
-                    self.inp.write   ( received + newline )
-                    self.buffer.write( received + newline )
+                    if len(received) >= 7 and received[0:7] == 'SLIMV::':
+                        command = received[7:]
+                        if len(command) >= 9 and command[0:9] == 'INTERRUPT':
+                            try:
+                                if mswindows:
+                                    import win32api
+                                    CTRL_C_EVENT = 0
+                                    win32api.GenerateConsoleCtrlEvent( CTRL_C_EVENT, 0 )
+                                else:
+                                    import signal
+                                    os.kill( self.pid, signal.SIGINT )
+                            except:
+                                # OK, at least we tried
+                                # Go on without interruption
+                                pass
+                        if len(command) >= 8 and command[0:8] == 'OUTPUT::':
+                            output_filename = command[8:]
+                            self.buffer.setfile( output_filename )
+                    else:
+                        # Fork here: write message to the stdin of REPL
+                        # and also write it to the display (display queue buffer)
+                        self.inp.write   ( received + newline )
+                        self.buffer.write( received + newline )
 
             conn.close()
 
                 break
 
 
-class buffer_listener( Thread ):
-    """Server thread to read and display contents of the output buffer.
-    """
-
-    def __init__ ( self, buffer ):
-        Thread.__init__( self )
-        self.buffer = buffer
-
-    def run( self ):
-        global terminate
-
-        while not terminate:
-            try:
-                # Constantly display messages in the display queue buffer
-                #TODO: it would be better having some wakeup mechanism here
-                time.sleep(0.01)
-                self.buffer.read_and_display( sys.stdout )
-
-            except:
-                # We just ignore any errors here
-                pass
-
-
 def server():
     """Main server routine: starts REPL and helper threads for
        sending and receiving data to/from REPL.
     # Start Lisp
     repl = Popen( cmd, stdin=PIPE, stdout=PIPE, stderr=STDOUT )
 
-    buffer = repl_buffer()
+    buffer = repl_buffer( sys.stdout )
 
     # Create and start helper threads
-    sl = socket_listener( repl.stdin, buffer )
+    sl = socket_listener( repl.stdin, buffer, repl.pid )
     sl.start()
     ol = output_listener( repl.stdout, buffer )
     ol.start()
-    bl = buffer_listener( buffer )
-    bl.start()
 
     # Allow Lisp to start, confuse it with some fancy Slimv messages
     sys.stdout.write( ";;; Slimv server is started on port " + str(PORT) + newline )
     sys.stdout.write( ";;; Slimv is spawning REPL..." + newline )
-    time.sleep(0.5)                         # wait for Lisp to start
-    buffer.read_and_display( sys.stdout )   # read Lisp startup messages
-    sys.stdout.write( ";;; Slimv connection established" + newline )
+    time.sleep(0.5)             # wait for Lisp to start
+    #sys.stdout.write( ";;; Slimv connection established" + newline )
 
     # Main server loop
     while not terminate:
             # to the stdin of REPL
             text = raw_input()
             repl.stdin.write( text + newline )
-            buffer.write( text + newline )
+            buffer.write( text + newline, True )
         except EOFError:
             # EOF (Ctrl+Z on Windows, Ctrl+D on Linux) pressed?
             terminate = 1
     """Displays program usage information.
     """
     progname = os.path.basename( sys.argv[0] )
-    print 'Usage: ', progname + ' [-d LEVEL] [-s] [-f FILENAME]'
+    print 'Usage: ', progname + ' [-d LEVEL] [-s] [-f INFILE]'
     print
     print 'Options:'
     print '  -?, -h, --help                show this help message and exit'
     python_path = sys.executable
     input_filename = ''
 
-
     # Always this trouble with the path/filenames containing spaces:
     # enclose them in double quotes
     if python_path.find( ' ' ) >= 0:

File plugin/slimv.vim

 " slimv.vim:    The Superior Lisp Interaction Mode for VIM
-" Version:      0.1.4
-" Last Change:  21 Feb 2009
+" Version:      0.2.0
+" Last Change:  28 Feb 2009
 " Maintainer:   Tamas Kovacs <kovisoft at gmail dot com>
 " License:      This file is placed in the public domain.
 "               No warranty, express or implied.
         " We don't have enough information to build client command
         return ''
     endif
-    if g:slimv_port == 5151
-        let port = ''
-    else
-        " Using port number other than default, must pass it to client
-        let port = ' -p ' . g:slimv_port
-    endif
+
+    " Start with the Python path
+    let cmd = g:slimv_python
+
+    " Add path of Slimv script, on Windows enclose in double quotes
     if g:slimv_windows
-        return g:slimv_python . ' "' . g:slimv_path . '"' . port  . ' -l ' . g:slimv_lisp
-        " This one can be used to start Lisp in a 'Console' window
-        " instead of the default DOS box
-        "return g:slimv_python . ' "' . g:slimv_path . '"' . port . ' -r ' .
-        "       \ '"console -w Slimv -r \"/k @p @s -l ' . g:slimv_lisp . ' -s\""'
+        let cmd = cmd . ' "' . g:slimv_path . '"'
     else
-        return g:slimv_python . ' ' . g:slimv_path . port . ' -l ' . g:slimv_lisp
+        let cmd = cmd . ' ' . g:slimv_path
+    endif
+
+    " Add port number if different from default
+    if g:slimv_port != 5151
+        let cmd = cmd . ' -p ' . g:slimv_port
+    endif
+
+    " Add Lisp path
+    let cmd = cmd . ' -l ' . g:slimv_lisp
+
+    return cmd
 endfunction
 
 function! SlimvClientCommand()
     call add( info,  printf( 'g:slimv_python        = %s',    g:slimv_python ) )
     call add( info,  printf( 'g:slimv_lisp          = %s',    g:slimv_lisp ) )
     call add( info,  printf( 'g:slimv_client        = %s',    g:slimv_client ) )
+    call add( info,  printf( 'g:slimv_repl_open     = %d',    g:slimv_repl_open ) )
+    call add( info,  printf( 'g:slimv_repl_dir      = %s',    g:slimv_repl_dir ) )
+    call add( info,  printf( 'g:slimv_repl_file     = %s',    g:slimv_repl_file ) )
+    call add( info,  printf( 'g:slimv_repl_split    = %d',    g:slimv_repl_split ) )
+    call add( info,  printf( 'g:slimv_repl_wait     = %d',    g:slimv_repl_wait ) )
     call add( info,  printf( 'g:slimv_keybindings   = %d',    g:slimv_keybindings ) )
     call add( info,  printf( 'g:slimv_menu          = %d',    g:slimv_menu ) )
     call SlimvLog( 1, info )
     let g:slimv_lisp = SlimvAutodetectLisp()
 endif
 
-" Name of the REPL buffer inside Vim
-if !exists( 'g:slimv_repl_name' )
-    let g:slimv_repl_name = 'Slimv.REPL'
+" Open a REPL buffer inside Vim?
+if !exists( 'g:slimv_repl_open' )
+    let g:slimv_repl_open = 1
 endif
 
+" Directory name for the REPL buffer file
+if !exists( 'g:slimv_repl_dir' )
+    if g:slimv_windows
+        let g:slimv_repl_dir = matchstr( tempname(), '.*\\' )
+    else
+        let g:slimv_repl_dir = '/tmp/'
+    endif
+endif
+
+" Filename for the REPL buffer file
+if !exists( 'g:slimv_repl_file' )
+    let g:slimv_repl_file = 'Slimv.REPL'
+endif
+
+" Shall we open REPL buffer in split window?
+if !exists( 'g:slimv_repl_split' )
+    let g:slimv_repl_split = 1
+endif
+
+" How many seconds to wait for the REPL output to finish?
+if !exists( 'g:slimv_repl_wait' )
+    let g:slimv_repl_wait = 10
+endif
 
 " Build client command (if not given in vimrc)
 if !exists( 'g:slimv_client' )
 
 
 " =====================================================================
+"  Other non-global script variables
+" =====================================================================
+
+" Name of the REPL buffer inside Vim
+let s:repl_name = g:slimv_repl_dir . g:slimv_repl_file
+
+" Lisp prompt in the last line
+let s:prompt = ''
+
+
+" =====================================================================
 "  General utility functions
 " =====================================================================
 
+" Position the cursor at the end of the REPL buffer
+" Optionally mark this position in Vim mark 's'
+function! SlimvEndOfReplBuffer( markit )
+    if !g:slimv_repl_open
+	" User does not want to display REPL in Vim
+	return
+    endif
+    normal G$
+    if a:markit
+        " Remember the end of the buffer: user may enter commands here
+        " Also remember the prompt, because the user may overwrite it
+        call setpos( "'s'", [0, line('$'), col('$'), 0] )
+        let s:prompt = getline( "'s'" )
+    endif
+    set nomodified
+endfunction
+
+" Reload the contents of the REPL buffer from the output file immediately
+function! SlimvRefreshReplBufferNow()
+    if !g:slimv_repl_open
+	" User does not want to display REPL in Vim
+	return
+    endif
+
+    "TODO: on error do this in a loop a couple of times
+    try
+        execute "silent edit! " . s:repl_name
+    catch /.*/
+        " Oops, something went wrong, let's try again after a short pause
+        sleep 1
+        execute "silent edit! " . s:repl_name
+    endtry
+    "TODO: use :read instead and keep only the delta in the readout file
+    if &endofline == 1
+        " Handle the situation when the last line is an empty line in REPL
+        " but Vim rejects to handle it as a separate line
+        call append( '$', "" )
+    endif
+    call SlimvEndOfReplBuffer( 1 )
+endfunction
+
+" Refresh REPL buffer continuously until no change is detected
+function! SlimvRefreshReplBuffer()
+    if !g:slimv_repl_open
+	" User does not want to display REPL in Vim
+	return
+    endif
+
+    " Refresh REPL buffer for a while until no change is detected
+    let lastline = line("$")
+    sleep 500m
+    call SlimvRefreshReplBufferNow()
+    let wait = g:slimv_repl_wait * 10   " number of cycles to wait for refreshing the REPL buffer
+    while line("$") > lastline && ( wait > 0 || g:slimv_repl_wait == 0 )
+        "TODO: Implement a custom main loop here, handling all Vim keypresses and commands
+        if getchar(1)
+            let c = getchar(0)
+            if c == 24
+                " Ctrl+B or Ctrl+C or Ctrl+X or Ctrl+Y pressed
+                call SlimvSend( ['SLIMV::INTERRUPT'], 0 )
+                let wait = g:slimv_repl_wait * 10
+            endif
+            if c == 27
+                " ESC pressed
+                let wait = 0
+                break
+            endif
+        endif
+        redraw
+        let lastline = line("$")
+        "TODO: customize the delay
+        sleep 100m
+        call SlimvRefreshReplBufferNow()
+        let wait = wait - 1
+    endwhile
+
+    if wait == 0
+        " Time is up and Lisp REPL still did not finish output
+        " Inform user about this and about the non-blocking and blocking refresh keys
+        if g:slimv_keybindings == 1
+            let refresh = "<Leader>z or <Leader>Z"
+        elseif g:slimv_keybindings == 2
+            let refresh = "<Leader>rw or <Leader>rr"
+        else
+            let refresh = ":call SlimvRefreshReplBuffer()"
+        endif
+        call append( '$', "Slimv warning: REPL is busy, refresh display with " . refresh )
+        call SlimvEndOfReplBuffer( 1 )
+    endif
+endfunction
+
 " Open a new REPL buffer or switch to the existing one
 function! SlimvOpenReplBuffer()
     "TODO: check if this works without 'set hidden'
-    "TODO: add option for split window
-    let repl_buf = bufnr( g:slimv_repl_name )
+    let repl_buf = bufnr( s:repl_name )
     if repl_buf == -1
         " Create a new REPL buffer
-        exe "edit " . g:slimv_repl_name
+        if g:slimv_repl_split
+            execute "split " . s:repl_name
+        else
+            execute "edit " . s:repl_name
+        endif
     else
-        " REPL buffer is already created. Check if it is open in a window
-        let repl_win = bufwinnr( repl_buf )
-        if repl_win != -1
-            " Switch to the REPL window
-            if winnr() != repl_win
-                exe repl_win . "wincmd w"
+        if g:slimv_repl_split
+            " REPL buffer is already created. Check if it is open in a window
+            let repl_win = bufwinnr( repl_buf )
+            if repl_win == -1
+                " Create windows
+                execute "split +buffer " . repl_buf
+            else
+                " Switch to the REPL window
+                if winnr() != repl_win
+                    execute repl_win . "wincmd w"
+                endif
             endif
         else
-            " Switch to the REPL buffer
-            exe "buffer " . repl_buf
+            execute "buffer " . repl_buf
         endif
     endif
-    
+
     " This buffer will not have an associated file
-    set buftype=nofile
+    inoremap <buffer> <silent> <CR> <End><CR><C-O>:call SlimvHandleCR()<CR>
+    inoremap <buffer> <silent> <expr> <BS> SlimvHandleBS()
+    inoremap <buffer> <silent> <Up> <C-O>:call SlimvHandleUp()<CR>
+    inoremap <buffer> <silent> <Down> <C-O>:call SlimvHandleDown()<CR>
+    noremap  <buffer> <silent> <C-X> :call SlimvHandleInterrupt()<CR>
+    inoremap <buffer> <silent> <C-X><C-X> <C-O>:call SlimvHandleInterrupt()<CR>
+    execute "au FileChangedShell " . g:slimv_repl_file . " :call SlimvRefreshReplBufferNow()"
+    execute "au FocusGained "      . g:slimv_repl_file . " :call SlimvRefreshReplBufferNow()"
+
+    redraw
+
+    call SlimvSend( ['SLIMV::OUTPUT::' . s:repl_name ], 0 )
+    call SlimvEndOfReplBuffer( 0 )
 endfunction
 
 " Select symbol under cursor and copy it to register 's'
 endfunction
 
 " Send argument to Lisp server for evaluation
-function! SlimvEval( args )
+function! SlimvSend( args, open_buffer )
     call SlimvClientCommand()
     if g:slimv_client == ''
         return
     endif
 
+    if a:open_buffer
+        call SlimvOpenReplBuffer()
+    endif
+
+    " Build a temporary file from the form to be evaluated
+    let ar = []
+    let i = 0
+    while i < len( a:args )
+        call extend( ar, split( a:args[i], '\n' ) )
+        let i = i + 1
+    endwhile
+
     let tmp = tempname()
     try
-        let ar = []
-        let i = 0
-        while i < len( a:args )
-            call extend( ar, split( a:args[i], '\n' ) )
-            let i = i + 1
-        endwhile
         call SlimvLog( 2, a:args )
         call writefile( ar, tmp )
+
+        " Send the file to the client for evaluation
         if g:slimv_debug_client == 0
             let result = system( g:slimv_client . ' -f ' . tmp )
         else
     finally
         call delete(tmp)
     endtry
+
+    if a:open_buffer
+        call SlimvRefreshReplBuffer()
+    endif
+endfunction
+
+" Eval arguments in Lisp REPL
+function! SlimvEval( args )
+    call SlimvSend( a:args, g:slimv_repl_open )
+endfunction
+
+" Recall command from the command history at the marked position
+function! SlimvSetCommandLine( cmd )
+    normal `s
+    let line = getline( "." )
+    if len( line ) > col( "'s" )
+        let line = strpart( line, 0, col( "'s" ) - 1 )
+    endif
+    let i = 0
+    let line = line . a:cmd
+    call setline( ".", line )
+    call SlimvEndOfReplBuffer( 0 )
+endfunction
+
+" Add command list to the command history
+function! SlimvAddHistory( cmd )
+    if !exists( 'g:slimv_cmdhistory' )
+        let g:slimv_cmdhistory = []
+    endif
+    let i = 0
+    while i < len( a:cmd )
+        call add( g:slimv_cmdhistory, a:cmd[i] )
+        let i = i + 1
+    endwhile
+    let g:slimv_cmdhistorypos = len( g:slimv_cmdhistory )
+endfunction
+
+" Recall command from the command history at the marked position
+function! SlimvRecallHistory()
+    if g:slimv_cmdhistorypos >= 0 && g:slimv_cmdhistorypos < len( g:slimv_cmdhistory )
+        call SlimvSetCommandLine( g:slimv_cmdhistory[g:slimv_cmdhistorypos] )
+    else
+        call SlimvSetCommandLine( "" )
+    endif
+endfunction
+
+" Handle insert mode 'Enter' keypress in the REPL buffer
+function! SlimvHandleCR()
+    let lastline = line( "'s" )
+    let lastcol  =  col( "'s" )
+    if lastline > 0
+        if line( "." ) >= lastline
+            " Trim the prompt from the beginning of the command line
+            " The user might have overwritten some parts of the prompt
+            let cmdline = getline( lastline )
+            let c = 0
+            while c < lastcol - 1 && cmdline[c] == s:prompt[c]
+                let c = c + 1
+            endwhile
+            let cmd = [ strpart( getline( lastline ), c ) ]
+
+            " Build a possible multi-line command
+            let l = lastline + 1
+            while l <= line("$") - 1
+                call add( cmd, strpart( getline( l ), 0) )
+                let l = l + 1
+            endwhile
+
+            " Evaluate the command
+            call SlimvAddHistory( cmd )
+            call SlimvEval( cmd )
+        endif
+    else
+        call append( '$', "Slimv error: previous EOF mark not found, re-enter last form:" )
+        call append( '$', "" )
+        call SlimvEndOfReplBuffer( 1 )
+    endif
+endfunction
+
+" Handle insert mode 'Backspace' keypress in the REPL buffer
+function! SlimvHandleBS()
+    if line( "." ) == line( "'s" ) && col( "." ) <= col( "'s" )
+        " No BS allowed before the previous EOF mark
+        return ""
+    else
+        return "\<BS>"
+    endif
+endfunction
+
+" Handle insert mode 'Up' keypress in the REPL buffer
+function! SlimvHandleUp()
+    if exists( 'g:slimv_cmdhistory' ) && line( "." ) == line( "'s" )
+        if g:slimv_cmdhistorypos > 0
+            let g:slimv_cmdhistorypos = g:slimv_cmdhistorypos - 1
+            call SlimvRecallHistory()
+        endif
+    endif
+endfunction
+
+" Handle insert mode 'Down' keypress in the REPL buffer
+function! SlimvHandleDown()
+    if exists( 'g:slimv_cmdhistory' ) && line( "." ) == line( "'s" )
+        if g:slimv_cmdhistorypos < len( g:slimv_cmdhistory )
+            let g:slimv_cmdhistorypos = g:slimv_cmdhistorypos + 1
+            call SlimvRecallHistory()
+        else
+            call SlimvSetCommandLine( "" )
+        endif
+    endif
+endfunction
+
+" Handle insert mode 'Down' keypress in the REPL buffer
+function! SlimvHandleInterrupt()
+    call SlimvSend( ['SLIMV::INTERRUPT'], 0 )
+    call SlimvRefreshReplBuffer()
 endfunction
 
 " Start and connect slimv server
 " This is a quite dummy function that just evaluates a comment
 function! SlimvConnectServer()
-    call SlimvEval([';;; Slimv client connected successfully'])
+    call SlimvEval( [';;; Slimv client connected successfully'] )
+endfunction
+
+" Refresh REPL buffer continuously
+function! SlimvRefresh()
+    if bufnr( s:repl_name ) == -1
+        " REPL not opened, no need to refresh
+        return
+    endif
+    if bufnr( s:repl_name ) != bufnr( "%" )
+        " REPL is not the current window, activate it
+        call SlimvOpenReplBuffer()
+    endif
+    call SlimvRefreshReplBuffer()
+endfunction
+
+" Refresh REPL buffer continuously
+function! SlimvRefreshNow()
+    if bufnr( s:repl_name ) == -1
+        " REPL not opened, no need to refresh
+        return
+    endif
+    if bufnr( s:repl_name ) != bufnr( "%" )
+        " REPL is not the current window, activate it
+        call SlimvOpenReplBuffer()
+    endif
+    call SlimvRefreshReplBufferNow()
 endfunction
 
 " Get the last region (visual block)
 if g:slimv_keybindings == 1
     " Short (one-key) keybinding set
 
-    noremap <Leader>S  :call SlimvConnectServer()<CR>
-    
     noremap <Leader>d  :<C-U>call SlimvEvalDefun()<CR>
     noremap <Leader>e  :<C-U>call SlimvEvalLastExp()<CR>
     noremap <Leader>E  :<C-U>call SlimvPprintEvalLastExp()<CR>
     noremap <Leader>s  :call SlimvDescribeSymbol()<CR>
     noremap <Leader>a  :call SlimvApropos()<CR>
 
+    noremap <Leader>S  :call SlimvConnectServer()<CR>
+    noremap <Leader>z  :call SlimvRefresh()<CR>
+    noremap <Leader>Z  :call SlimvRefreshNow()<CR>
+    
 elseif g:slimv_keybindings == 2
     " Easy to remember (two-key) keybinding set
 
-    " Connection commands
-    noremap <Leader>cs  :call SlimvConnectServer()<CR>
-    
     " Evaluation commands
     noremap <Leader>ed  :<C-U>call SlimvEvalDefun()<CR>
     noremap <Leader>ee  :<C-U>call SlimvEvalLastExp()<CR>
     noremap <Leader>ds  :call SlimvDescribeSymbol()<CR>
     noremap <Leader>da  :call SlimvApropos()<CR>
 
+    " REPL commands
+    noremap <Leader>rc  :call SlimvConnectServer()<CR>
+    noremap <Leader>rr  :call SlimvRefresh()<CR>
+    noremap <Leader>rn  :call SlimvRefreshNow()<CR>
+    
 endif
 
 " =====================================================================
     
     menu &Slimv.&Documentation.Describe-&Symbol        :call SlimvDescribeSymbol()<CR>
     menu &Slimv.&Documentation.&Apropos                :call SlimvApropos()<CR>
+    
+    menu &Slimv.&REPL.&Connect-Server                  :call SlimvConnectServer()<CR>
+    menu &Slimv.&REPL.&Refresh                         :call SlimvRefresh()<CR>
+    menu &Slimv.&REPL.&Refresh-Now                     :call SlimvRefreshNow()<CR>
 endif