Commits

Tamas Kovacs committed b8a1e48

Version 0.3.0

REPL buffer enhancements: added syntax highlighting and automatic indentation, it is possible to enter a multi-line command, Ctrl-C is working; server REPL window performance enhancement on Linux.

  • Participants
  • Parent commits 0503dc2

Comments (0)

Files changed (3)

-*slimv.txt*                    Slimv                 Last Change: 08 Mar 2009
+*slimv.txt*                    Slimv                 Last Change: 16 Mar 2009
 
 Slimv                                                                  *slimv*
-                               Version 0.2.2
+                               Version 0.3.0
 
 The Superior Lisp Interaction Mode for Vim.
 This plugin is aimed to help Lisp development by interfacing between Vim and
 |slimv-usage|                Usage
 |slimv-repl|                 Lisp REPL inside Vim
 |slimv-clojure|              Clojure support
+|slimv-external|             External utilities
 |slimv-faq|                  Frequently Asked Questions
 |slimv-changelog|            Change Log
 |slimv-issues|               Known Issues
 INSTALLATION                                               *slimv-installation*
 
 Prerequisites:
+
   - Lisp (any Common Lisp implementation should be OK) or Clojure installed.
   - Python 2.4 or later installed.
   - The interrupt functionality needs the pywin32 extension on Windows.
 
 To install the script:
+
   - Download the slimv.zip.
   - Extract the zip archive into your vimfiles or runtime directory.
     See Vim help file |usr_05.txt| for details on adding a plugin.
     See |slimv-customization| below on how to do this.
 
 Upgrade from previous script versions:
-  No special upgrade actions are required.
+
+0.1   - 0.2.x    Users having a custom |g:slimv_repl_file| setting should
+                 consider attaching the .lisp or .clj extension to the name
+                 of the REPL buffer, in order to have syntax highlighting
+                 and autoindenting in the REPL buffer.
+                 Users who already have done this are smarter than me :)
 
 ===============================================================================
 CUSTOMIZATION                                             *slimv-customization*
 |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_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.
 All you need to know then is the list of possible Slimv commands, how to
 enter them and under what conditions.
 
-It is possible to interrupt a running Lisp program by pressing Ctrl+C,
+It is possible to interrupt a running Lisp program by pressing Ctrl-C,
 at least in some Lisp implementations, like CLISP (does not work for example
 with SBCL).
 
-To end the Lisp session press EOF (Ctrl+D on Linux, Ctrl+Z on Windows)
+To end the Lisp session press EOF (Ctrl-D on Linux, Ctrl-Z on Windows)
 in the Lisp REPL window. After exiting the REPL it is possible to open
 a new one from Vim the same way as before.
 
 
 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).
+Outside of the command line the <Up> and <Down> keys move the cursor,
+as usual.
 
 The keys with modified meanings in the Lisp REPL buffer are listed below:
 
                    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:
+                   REPL when in the command line.
 
-    <C-X>          Sends a Ctrl+C (SIGINT) signal to the Lisp REPL (see below).
+    <Down>         Brings up the next command typed and sent to the Lisp REPL
+                   when in the command line.
 
 While waiting for REPL output:
 
-    <C-X>          Sends a Ctrl+C (SIGINT) signal to the Lisp REPL. Useful for
+    CTRL-C         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.
+                   buffer.
 
-    <ESC>          Stops waiting for REPL output, returns to regular Vim mode.
+    any other key  Stops waiting for REPL output, returns to regular Vim mode.
+                   The key that was pressed is translated and processed by Vim,
+                   according to current the Vim mode.
                    It is possible to return to the continuous refresh mode
                    by selecting the "Refresh REPL Buffer" command.
 
 
 
 ===============================================================================
+EXTERNAL UTILITIES                                             *slimv-external*
+
+This section is about utilities, settings, etc., not related strongly to Slimv,
+but may be used to aim Lisp development. These are mostly built-in Vim features
+or options, and sometimes external Vim plugins.
+Slimv does not want to copy these functionalities, if they exist and work well.
+
+
+1. Syntax highlighting
+
+The syntax highlighting is done via the default lisp.vim syntax plugin.
+For Clojure files one has two options:
+a. use the Lisp filetype also for Clojure files (that approach is used by Slimv
+   for the REPL buffer if no other filetype is set)
+b. install a Clojure Vim syntax plugin, like VimClojure.
+
+
+2. Indentation
+
+The indentation is also done via the default lisp.vim indent plugin, or an
+optionally installed Clojure indent plugin, just like for the syntax
+highlighting.
+There are some built-in Vim reindentation commands that may come very handy
+when editing Lisp files. One can define a custom key mapping for any of them,
+such mappings are not defined by Slimv.
+
+    =                       Reindent selection, after a text has been selected.
+
+    ==                      Reindent current line.
+
+    [(v%=                   Select current form and reindent it.
+
+    99[(v%=                 Select top level form and reindent it.
+
+
+3. Parenthesis handling
+
+Vim obtains many tools to aid working with parentheses. This is a very important
+topic for a Lisp programmer.
+
+    :inoremap ( ()<Esc>i    Automatically insert closing paren when an opening
+                            one is inserted.
+
+    :inoremap [ []<Esc>i    Same as above but for brackets.
+
+    :set showmatch          Briefly jump with the cursor to the matching paren
+                            or bracket when a closing pair is inserted.
+
+    %                       Go to the matching paren or bracket.
+
+    :source $VIMRUNTIME/macros/matchit.vim
+                            Adds extended matching with "%" to Vim.
+
+    [(v                     Select current form.
+
+    99[(v                   Select top level form.
+
+    g:lisp_rainbow          Colorize differing levels of parenthesization with
+                            different highlighting. Currently works only for
+                            the 'lisp' filetype, hopefully it will be added
+                            soon to the Clojure plugins as well.
+
+
+4. Completion
+
+    CTRL-N                  The built-in Vim keyword completion is a very handy
+    CTRL-P                  feature. You start typing a word, and when CTRL-P
+                            or CTRL-N is pressed, then Vim looks up the keyword
+                            starting with the same letters as typed in up or
+                            down direction.
+
+    set complete            The |'complete'| option controls how keyword
+                            completion works.
+
+
+5. Tag lookup
+
+    |ctags|                 "Exuberant ctags" is a powerful utility for
+                            generating tag database for different kind of
+			    programming languages, including Lisp. Tag lookup
+                            is then done via the CTRL-] (or :tag) command,
+                            return to the previous positon with CTRL-T.
+
+    ctags --language-force=lisp *.lisp *.clj
+                            This or a similar command may be used to generate
+                            tags file from .lisp and .clj files in a directory.
+
+    :execute '!ctags --language-force=lisp *.lisp *.clj'
+                            This is the same command as the previous one, but
+                            started from inside Vim, rather than from the OS
+                            command prompt, assuming that ctags.exe is stored
+                            in the PATH.
+
+
+===============================================================================
 FAQ                                                                 *slimv-faq*
 
 - Q: Why is this plugin called 'Slimv'?
      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.
+     truth). There is definitely room for improvement.
+     It would be nice to make Vim as usable as Emacs for Lisp programming.
      In my opinion the main barrier is the lack of asynchronous buffer update
      in Vim, but this may change in the future.
 
 - Q: Why is the default port number 5151?
 - A: Hint: what roman numerals are 5,1,5,1? Bingo: VI, doubled.
 
+- Q: What is the version numbering concept?
+- A: <major version>.<minor version>.<bugfix release>, where:
+     major  version: Let's talk about it when it reaches 1...
+     minor  version: New functionalities added, that are worth mentioning.
+     bugfix release: Only bugfixes or tiny additions.
+
 - Q: Are you a Lisp expert?
 - A: No, not at all. I'm just learning Lisp. Also just learning Vim
      scripting. And I'm not a Python expert either, however (at the moment)
      distributions contain a Python interpreter with high enough version.
      On Windows this means, you need to install Python, if you don't have
      one (at least version 2.4). Anyway, Python is a nice language and
-     also a perfect replacement for calculator.exe :-)
+     also a perfect replacement for calculator.exe :)
 
 ===============================================================================
 CHANGE LOG                                                    *slimv-changelog*
 
+0.3.0  - Added syntax highlighting and automatic indentation for the REPL
+         buffer (needs lisp and/or clojure Vim plugins).
+       - It is possible to enter a multi-line command in the REPL buffer,
+         until the opening and closing parens match.
+       - Insert mode Up and Down keys move cursor when outside of the REPL
+         command line.
+       - Ctrl-C is working inside the REPL buffer (while waiting for output),
+         so Ctrl-X and Ctrl-X Ctrl-X keybindings are removed.
+       - REPL window performance enhancement on Linux.
+
 0.2.2  - Fixed REPL input and output mix-up problems.
        - Evaluation performance enhancement.
        - Corrected some more macroexpand problems.
 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
+       - Ctrl-C is propagated to Lisp REPL, so it is possible to interrupt
          a running program. Does not work however with some Lisp
          implementations (like SBCL).
 
 - 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).
+- Sometimes a Python exception happens after a CTRL-C inside the REPL buffer
+  followed by an EOF (CTRL-Z or CTRL-D) in the external REPL window.
 - 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.
+- Interruption (Ctrl-C) does not work with all Lisp implementations.
+  For example SBCL exits on Windows when receiving Ctrl-C.
   It does not work in Clojure.
 - There are some functions that are not compatible or simply not working for
   Clojure.
 ===============================================================================
 TODO                                                               *slimv-todo*
 
-- Make the script a lisp filetype plugin.
+- Make the script a lisp+clojure 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
 Also special thanks to Eric Marsden and all the Emacs/SLIME developers for
 making SLIME.
 Last but not least many thanks to my wife Andrea (for the Italians out there:
-hey, this is a female name in Hungary :-) for her support and patience.
+hey, this is a female name in Hungary :) for her support and patience.
 
 ===============================================================================
 vim:tw=78:noet:wrap:ts=8:ft=help:norl:
 #
 # Client/Server code for Slimv
 # slimv.py:     Client/Server code for slimv.vim plugin
-# Version:      0.2.2
-# Last Change:  08 Mar 2009
+# Version:      0.3.0
+# Last Change:  16 Mar 2009
 # Maintainer:   Tamas Kovacs <kovisoft at gmail dot com>
 # License:      This file is placed in the public domain.
 #               No warranty, express or implied.
                             # Semaphore to synchronize access to the global display queue
 
     def setfile( self, filename ):
-        """Set output filename
+        """Set output filename. Greet user if this is the first time.
         """
         self.sema.acquire()
+        oldname = self.filename
         self.filename = filename
+        if oldname == '':
+            self.write_nolock( newline + ';;; Slimv client is connected to REPL on port ' + str(PORT) + '.' + newline, True )
+            user = None
+            if mswindows:
+                user = os.getenv('USERNAME')
+            else:
+                user = os.getenv('USER')
+            if not user:
+                self.write_nolock( ';;; This could be the start of a beautiful program.' + newline + newline, True )
+            else:
+                self.write_nolock( ';;; ' + user + ', this could be the start of a beautiful program.' + newline + newline, True )
         self.sema.release()
 
     def writebegin( self ):
                     finally:
                         file.close()
                     tries = 0
+                except IOError:
+                    tries = tries - 1
+                    if tries == 0:
+                        traceback.print_exc()
                 except:
-                    #traceback.print_exc()
                     tries = tries - 1
         elif len( self.buffer ) < 2000:
             # No filename supplied, collect output info a buffer until filename is given
                         # Fork here: write message to the stdin of REPL
                         # and also write it to the display (display queue buffer)
                         self.buffer.writebegin()
-#                        self.buffer.write_nolock( received + newline )
-#                        self.inp.write( received + newline )
                         self.buffer.write_nolock( received )
                         self.inp.write( received )
                         self.buffer.writeend()
                     else:
                         self.buffer.write( c )
                 else:
-                    # On Linux set read mode to non blocking
-                    import fcntl, select
-                    flag = fcntl.fcntl(self.out.fileno(), fcntl.F_GETFL)
-                    fcntl.fcntl(self.out.fileno(), fcntl.F_SETFL, flag | os.O_NONBLOCK)
-
-                    r = select.select([self.out.fileno()], [], [], 0)[0]
-                    if r:
-                        c = os.read( self.out.fileno(), 1 )
-                        self.buffer.write( c )
+                    c = self.out.read( 1 )
+                    self.buffer.write( c )
             except:
-                break
+                terminate = 1
 
 
 def server():
 
     # 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 )
+    sys.stdout.write( ";;; Slimv is spawning REPL..." + newline + newline )
     time.sleep(0.5)             # wait for Lisp to start
     #sys.stdout.write( ";;; Slimv connection established" + newline )
 
 " slimv.vim:    The Superior Lisp Interaction Mode for VIM
-" Version:      0.2.2
-" Last Change:  08 Mar 2009
+" Version:      0.3.0
+" Last Change:  15 Mar 2009
 " Maintainer:   Tamas Kovacs <kovisoft at gmail dot com>
 " License:      This file is placed in the public domain.
 "               No warranty, express or implied.
         " Try to find Clojure on the standard installation places
         let lisps = split( globpath( 'c:/*clojure*', 'clojure.jar' ), '\n' )
         if len( lisps ) > 0
-	    return '"java -cp ' . lisps[0] . ' clojure.lang.Repl"'
+            return '"java -cp ' . lisps[0] . ' clojure.lang.Repl"'
         endif
     endif
 
 " Get the filetype (Lisp dialect) used by Slimv
 function! SlimvGetFiletype()
     if exists( 'g:slimv_filetype' )
-	" Return Slimv filetype if defined
-	return g:slimv_filetype
+        " Return Slimv filetype if defined
+        return g:slimv_filetype
     endif
 
     if &ft != ''
-	" Return Vim filetype if defined
-	return &ft
+        " Return Vim filetype if defined
+        return &ft
     endif
 
     " We have no clue, guess its lisp
 
 " Filename for the REPL buffer file
 if !exists( 'g:slimv_repl_file' )
-    let g:slimv_repl_file = 'Slimv.REPL'
+    if SlimvGetFiletype() == 'clojure'
+        let g:slimv_repl_file = 'Slimv.REPL.clj'
+    else
+        let g:slimv_repl_file = 'Slimv.REPL.lisp'
+    endif
 endif
 
 " Shall we open REPL buffer in split window?
 " 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
+        " User does not want to display REPL in Vim
+        return
     endif
     normal G$
     if a:markit
         " Also remember the prompt, because the user may overwrite it
         call setpos( "'s'", [0, line('$'), col('$'), 0] )
         let s:prompt = getline( "'s'" )
+        if s:insertmode
+            " Hacking: we add a space at the end of the last line
+            " so that the cursor remains in correct position after insertmode eval
+            "call setline( "'s", s:prompt . " " )
+        endif
     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
+        " User does not want to display REPL in Vim
+        return
     endif
 
     if bufnr( s:repl_name ) != bufnr( "%" )
         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
+        " Oops, something went wrong, the buffer will not be refreshed this time
     endtry
+    syntax on
     "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( '$', "" )
+        try
+            call append( '$', "" )
+        catch /.*/
+            " OK, we cannot append right now, the server is probably busy with
+            " updating the REPL file. Just go on, it's not that important.
+        endtry
     endif
     call SlimvEndOfReplBuffer( 1 )
 endfunction
 
+" Send interrupt command to REPL
+function! SlimvInterrupt()
+    call SlimvSend( ['SLIMV::INTERRUPT'], 0 )
+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
+        " 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
+    let ftime = getftime( s:repl_name )
+    let lastftime = ftime
+    sleep 200m
     call SlimvRefreshReplBufferNow()
+
+    let save_ve = &ve
+    if s:insertmode
+        " We are in insert mode, let's fake a movement to the right
+        " in order to display the cursor at the right place.
+        " For this we need to set the virtualedit=all option temporarily
+        echon '-- INSERT --'
+        set ve=all
+        normal l
+    else
+        " Inform user that we are in running mode (waiting for REPL output)
+        echon '-- RUNNING --'
+    endif
+    let interrupt = 0
     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
+    try
+        while wait > 0 || g:slimv_repl_wait == 0
+            let m = '/\%#/'
+            silent! execute 'match Cursor ' . m
+            match Cursor /\%#/
+            redraw
+            if getchar(1)
+                break
+            endif
+            sleep 100m
+            let lastftime = ftime
+            let ftime = getftime( s:repl_name )
+            if ftime != lastftime || ftime == localtime()
+                " REPL buffer file changed recently, reload it
+                call SlimvRefreshReplBufferNow()
+            endif
+            if g:slimv_repl_wait != 0
+                let wait = wait - 1
+            endif
+        endwhile
+    catch /^Vim:Interrupt$/
         if getchar(1)
+            " Swallow interrupt key
             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
+            if c == 3
+                " Yes, this was the Ctrl-C, propagate it to the server
+                let interrupt = 1
+                call SlimvHandleInterrupt()
             endif
         endif
-        redraw
-        let lastline = line("$")
-        "TODO: customize the delay
-        sleep 100m
-        call SlimvRefreshReplBufferNow()
-        let wait = wait - 1
-    endwhile
+    endtry
+
+    " Restore everything
+    silent! execute 'match None ' . m
+    if !interrupt
+        let s:insertmode = 0
+    endif
+    echon '            '
+    let &ve = save_ve
 
-    if wait == 0
+    if wait == 0 && ftime != lastftime
         " 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
     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()"
     execute "au BufEnter "         . g:slimv_repl_file . " :call SlimvRefreshReplBufferNow()"
 
+    filetype on
     redraw
 
     call SlimvSend( ['SLIMV::OUTPUT::' . s:repl_name ], 0 )
 
 " Set command line after the prompt
 function! SlimvSetCommandLine( cmd )
-    normal `s
     let line = getline( "." )
-    if len( line ) > col( "'s" )
-        let line = strpart( line, 0, col( "'s" ) - 1 )
+    if line( "." ) == line( "'s" )
+        " The prompt is in the line marked with 's
+        let promptlen = len( s:prompt )
+    else
+        let promptlen = 0
+    endif
+    if len( line ) > promptlen
+        let line = strpart( line, 0, promptlen )
     endif
-    let i = 0
     let line = line . a:cmd
     call setline( ".", line )
     call SlimvEndOfReplBuffer( 0 )
     endif
     let i = 0
     while i < len( a:cmd )
-        call add( g:slimv_cmdhistory, a:cmd[i] )
+        " Trim trailing whitespaces from the command
+        let command = substitute( a:cmd[i], "\\(.*[^ ]\\)\\s*", "\\1", "g" )
+        call add( g:slimv_cmdhistory, command )
         let i = i + 1
     endwhile
     let g:slimv_cmdhistorypos = len( g:slimv_cmdhistory )
     endif
 endfunction
 
+" Count the opening and closing parens or brackets to determine if they match
+function! s:GetParenCount( lines )
+    let paren = 0
+    let inside_string = 0
+    let i = 0
+    while i < len( a:lines )
+        let inside_comment = 0
+        let j = 0
+        while j < len( a:lines[i] )
+            if inside_string
+                " We are inside a string, skip parens, wait for closing '"'
+                if a:lines[i][j] == '"'
+                    let inside_string = 0
+                endif
+            elseif inside_comment
+                " We are inside a comment, skip parens, wait for end of line
+            else
+                " We are outside of strings and comments, now we shall count parens
+                if a:lines[i][j] == '"'
+                    let inside_string = 1
+                endif
+                if a:lines[i][j] == ';'
+                    let inside_comment = 1
+                endif
+                if a:lines[i][j] == '(' || a:lines[i][j] == '['
+                    let paren = paren + 1
+                endif
+                if a:lines[i][j] == ')' || a:lines[i][j] == ']'
+                    let paren = paren - 1
+                    if paren < 0
+                        " Oops, too many closing parens in the middle
+                        return paren
+                    endif
+                endif
+            endif
+            let j = j + 1
+        endwhile
+        let i = i + 1
+    endwhile
+    return paren
+endfunction
+
 " Handle insert mode 'Enter' keypress in the REPL buffer
 function! SlimvHandleCR()
     let lastline = line( "'s" )
                 let l = l + 1
             endwhile
 
-            " Evaluate the command
-            call SlimvAddHistory( cmd )
-            call SlimvEval( cmd )
+            " Count the number of opening and closing braces
+            let paren = s:GetParenCount( cmd )
+            if paren == 0
+                " Expression finished, let's evaluate it
+                " but first add it to the history
+                let s:insertmode = 1
+                call SlimvAddHistory( cmd )
+                call SlimvEval( cmd )
+            elseif paren < 0
+                " Too many closing braces
+                let dummy = input( "Too many closing parens found. Press ENTER to continue." )
+            else
+                " Expression is not finished yet, indent properly and wait for completion
+                " Indentation works only if lisp indentation is switched on
+                let indent = ''
+                let i = lispindent( '.' )
+                while i > 0
+                    let indent = indent . ' '
+                    let i = i - 1
+                endwhile
+                call setline( ".", indent )
+                call SlimvEndOfReplBuffer( 0 )
+            endif
         endif
     else
         call append( '$', "Slimv error: previous EOF mark not found, re-enter last form:" )
 
 " Handle insert mode 'Up' keypress in the REPL buffer
 function! SlimvHandleUp()
-    if exists( 'g:slimv_cmdhistory' ) && line( "." ) == line( "'s" )
-        if g:slimv_cmdhistorypos > 0
+    if line( "." ) >= line( "'s" )
+        if exists( 'g:slimv_cmdhistory' ) && g:slimv_cmdhistorypos > 0
             let g:slimv_cmdhistorypos = g:slimv_cmdhistorypos - 1
             call SlimvRecallHistory()
         endif
+    else
+        normal k
     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 )
+    if line( "." ) >= line( "'s" )
+        if exists( 'g:slimv_cmdhistory' ) && g:slimv_cmdhistorypos < len( g:slimv_cmdhistory )
             let g:slimv_cmdhistorypos = g:slimv_cmdhistorypos + 1
             call SlimvRecallHistory()
         else
             call SlimvSetCommandLine( "" )
         endif
+    else
+        normal j
     endif
 endfunction
 
     else
         " The form is a 'defmacro', so do a macroexpand from the macro name and parameters
         if SlimvGetFiletype() == 'clojure'
-	    " Some Vim configs (e.g. matchit.vim) include the trailing ']' after '%' in Visual mode
+            " Some Vim configs (e.g. matchit.vim) include the trailing ']' after '%' in Visual mode
             normal vt[%ht]"sy
         else
             normal vt(])"sy
         let m = substitute( m, "defmacro\\s*", a:command . " '(", 'g' )
         if SlimvGetFiletype() == 'clojure'
             " Remove opening bracket from the parameter list
-	    " TODO: fix this for multi-line macro header
+            " TODO: fix this for multi-line macro header
             let m = substitute( m, "\\[\\(.*\\)", "\\1", 'g' )
-	else
+        else
             " Remove opening brace from the parameter list
-	    " The nice regular expression below says: remove the third '('
-	    " ( + something + ( + something + ( + something -> ( + something + ( + something + something
-	    " TODO: fix this for multi-line macro header
+            " The nice regular expression below says: remove the third '('
+            " ( + something + ( + something + ( + something -> ( + something + ( + something + something
+            " TODO: fix this for multi-line macro header
             let m = substitute( m, "\\(([^()]*([^()]*\\)(\\(.*\\)", "\\1\\2", 'g' )
         endif
     endif
     noremap <Leader>b  :<C-U>call SlimvEvalBuffer()<CR>
     noremap <Leader>v  :call SlimvInteractiveEval()<CR>
     noremap <Leader>u  :call SlimvUndefineFunction()<CR>
-    
+
     noremap <Leader>1  :<C-U>call SlimvMacroexpand()<CR>
     noremap <Leader>m  :<C-U>call SlimvMacroexpandAll()<CR>
     noremap <Leader>t  :call SlimvTrace()<CR>
     noremap <Leader>T  :call SlimvUntrace()<CR>
     noremap <Leader>l  :call SlimvDisassemble()<CR>
     noremap <Leader>i  :call SlimvInspect()<CR>
-    
+
     noremap <Leader>D  :<C-U>call SlimvCompileDefun()<CR>
     noremap <Leader>L  :<C-U>call SlimvCompileLoadFile()<CR>
     noremap <Leader>F  :<C-U>call SlimvCompileFile()<CR>
     noremap <Leader>R  :call SlimvCompileRegion()<CR>
-    
+
     noremap <Leader>p  :call SlimvProfile()<CR>
     noremap <Leader>P  :call SlimvUnprofile()<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
 
     noremap <Leader>eb  :<C-U>call SlimvEvalBuffer()<CR>
     noremap <Leader>ei  :call SlimvInteractiveEval()<CR>
     noremap <Leader>eu  :call SlimvUndefineFunction()<CR>
-    
+
     " Debug commands
     noremap <Leader>m1  :<C-U>call SlimvMacroexpand()<CR>
     noremap <Leader>ma  :<C-U>call SlimvMacroexpandAll()<CR>
     noremap <Leader>du  :call SlimvUntrace()<CR>
     noremap <Leader>dd  :call SlimvDisassemble()<CR>
     noremap <Leader>di  :call SlimvInspect()<CR>
-    
+
     " Compile commands
     noremap <Leader>cd  :<C-U>call SlimvCompileDefun()<CR>
     noremap <Leader>cl  :<C-U>call SlimvCompileLoadFile()<CR>
     noremap <Leader>cf  :<C-U>call SlimvCompileFile()<CR>
     noremap <Leader>cr  :call SlimvCompileRegion()<CR>
-    
+
     " Profile commands
     noremap <Leader>pp  :call SlimvProfile()<CR>
     noremap <Leader>pu  :call SlimvUnprofile()<CR>
-    
+
     " Documentation commands
     noremap <Leader>ds  :call SlimvDescribeSymbol()<CR>
     noremap <Leader>da  :call SlimvApropos()<CR>
     noremap <Leader>rc  :call SlimvConnectServer()<CR>
     noremap <Leader>rr  :call SlimvRefresh()<CR>
     noremap <Leader>rn  :call SlimvRefreshNow()<CR>
-    
+
 endif
 
 " =====================================================================
     menu &Slimv.&Evaluation.Eval-&Buffer               :<C-U>call SlimvEvalBuffer()<CR>
     menu &Slimv.&Evaluation.Interacti&ve-Eval\.\.\.    :call SlimvInteractiveEval()<CR>
     menu &Slimv.&Evaluation.&Undefine-Function         :call SlimvUndefineFunction()<CR>
-    
+
     menu &Slimv.De&bugging.Macroexpand-&1              :<C-U>call SlimvMacroexpand()<CR>
     menu &Slimv.De&bugging.&Macroexpand-All            :<C-U>call SlimvMacroexpandAll()<CR>
     menu &Slimv.De&bugging.&Trace\.\.\.                :call SlimvTrace()<CR>
     menu &Slimv.De&bugging.U&ntrace\.\.\.              :call SlimvUntrace()<CR>
     menu &Slimv.De&bugging.Disassemb&le\.\.\.          :call SlimvDisassemble()<CR>
     menu &Slimv.De&bugging.&Inspect\.\.\.              :call SlimvInspect()<CR>
-    
+
     menu &Slimv.&Compilation.Compile-&Defun            :<C-U>call SlimvCompileDefun()<CR>
     menu &Slimv.&Compilation.Compile-&Load-File        :<C-U>call SlimvCompileLoadFile()<CR>
     menu &Slimv.&Compilation.Compile-&File             :<C-U>call SlimvCompileFile()<CR>
     menu &Slimv.&Compilation.Compile-&Region           :call SlimvCompileRegion()<CR>
-    
+
     menu &Slimv.&Profiling.&Profile\.\.\.              :call SlimvProfile()<CR>
     menu &Slimv.&Profiling.&Unprofile\.\.\.            :call SlimvUnprofile()<CR>
-    
+
     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>