tclreadline

Package information

Original tclreadline

What tclreadline
Where http://tclreadline.sourceforge.net/
Description Tcl bindings for GNU readline, by Johannes Zellner, used to enhance an interactive Tcl.
Latest version 2.1.0
Updated 07/2001

More recently updated forks

Use example

If you put the following in your .tclshrc file in your home directory then when you run Tcl interactively you'll get tclreadline support, with a fancy prompt showing you what machine you're on and the last directory of your current directory:

package require tclreadline

proc ::tclreadline::prompt1 {} {
    return "[lindex [split [info hostname] "."] 0] [lindex [split [pwd] "/"] end] % "
}

::tclreadline::Loop

You can access your Tcl command line history using vi or emacs-style keystrokes by creating a .inputrc file in your home directory and putting a line it it that says "set editing-mode vi" or "set editing-mode emacs".

ccbbaa 20210625 Update: how to quickly provide tclreadline access in tclsh and wish on Linux (where native console is not present, and history etc. functions are missing on shell invocation of the above):

# copy this file to ~/.wishrc and or ~/.tclshrc -- it will prompt you to enter
# the tclreadline if you want to, iff wish or tclsh are started without args.
# ccbbaa 20210625 ; ref: https://wiki.tcl-lang.org/page/tclreadline
if {![llength $argv]} {
  puts "type `tclreadline::Loop` to start tclreadline"
  namespace eval ::tclreadline {
    proc ::tclreadline::Loop {} {
      package require tclreadline
      proc ::tclreadline::prompt1 {} {
        return "[lindex [split [info hostname] "."] 0] [lindex [split [pwd] "/"] end] % "
      }
      tailcall ::tclreadline::Loop
    }
  }
}

Implementing completion yourself with an old version of tclreadline

The readline procs can be used to create for instance a basic Tcl sh with command completion:

package require tclreadline


# This proc returns the larges common startstring in a list, e.g.:
# for {list link} it will return li
 
namespace eval readline {
    namespace export readline history
    proc largest_common_substring { word matches } {
        # returns a list with the largest common substring
        if {[llength $matches]==1} {
            return [list [lindex $matches 0] $matches]
        }
        set subword $word
        while {[llength [lsearch -inline -all $matches $subword*]]==[llength $matches]} {
            if {$subword eq [lindex $matches 0]} break;
            set word $subword
            set subword [string range [lindex $matches 0] 0 [string length $word]]
        }
        return [list $word $matches]

    }
}

readline::history read ~/.sh_history ; # read saved history

# unknown should behave as in an interactive tclsh
set tcl_interactive 1 ; info script ""

# save history before exit use hide instead if rename so no _exit is show in [info commands]
interp hide {} exit
 
proc exit {args} {
     readline::history write ~/.sh_history
     interp invokehidden {} exit
}

# Define completion proc
# The completion proc is called from readline with the arguments:
# line:  The complete line in the readline buffer
# word:  The word that is being completed
# start: The start index of the word in line
# end:   The end index of the word in line
#
# A completion proc returns a list with two elements 
# 0: The text that will replace the word that is being completed
# 1: A list of all possible matches for the word that is currently being completed

proc complete {line word start end} {
    set matches {}
    if {[string index $word 0] eq {$}} {
        # variable completion
        set var_name [string range $word 1 end]
        foreach var [uplevel #0 [list info vars ${var_name}*]] {
            lappend matches \$[set var]
        }
    } elseif {$word eq $line} {
        # command completion
        set matches [uplevel #0 [list info commands $word*]]
        foreach ns [namespace children ::] {
            if {[string match $word* $ns]!=0} {
                lappend matches $ns
            }
        }
    } else {
        foreach file [glob -nocomplain $word*] {
            string map [list [file normalize ~] ~] $file
            lappend matches [string map  {{ } {\ }} $file]
        }
        foreach file [glob -nocomplain -type hidden $word*] {
            string map [list [file normalize ~] ~] $file
            lappend matches [string map  {{ } {\ }} $file]
        }
    }
    # suppress space
    if {[llength $matches] == 1} {
        return [list [lindex $matches 0] [list {}]]
    }
    return [::readline::largest_common_substring $word $matches]
} 

 
# register compeletion proc, completion can be disabled by readline::completion {}

readline::completion complete
    
# command loop
while {1} {
    set command [readline::readline "([file tail [pwd]]) % "]
    while {![info complete $command]} {
     set command $command\n[readline::readline "> "]
    }
    readline::history add $command
    catch [eval $command] result
    if {($result ne "") && ([string range $command end-1 end] ne ";;")} { 
        puts $result
    }
}

Notes

slebetman notes that for the Win32 platform, readline is not necessary as native tclsh on Win32 already have line editing capability including history and history substitution (which I believe is due to DOSKEY). People usually want readline for Tcl when they are on Unix.

MJ agrees that on Windows it is not necessary per se, but I like a consistent interface in my application regardless if it is run on Linux or Windows. Readline keyboard shortcuts have a tendency to stick in your fingers, which makes working with a Windows CLI application painful. Also note that the code below should be easy to adapt to Linux (probably only removing the __declspec(dllexport)) giving you a binding to readline on Linux.

MJ -- In the previous build an access violation occured on the free(line_read) this was caused by the fact that the dll linked to msvcr70.dll and msvcrt.dll at the same time. malloc was used from the one dll and free from the other resulting in a crash. The current dll at the url above only links to msvcrt.dll, solving the problem.

MJ -- 14/08/2006 -- The newer version includes history modification commands and allows the readline completion to be performed by a Tcl proc. This allows integration into your own Tcl programs where the default readline completions don't make sense. It has been tested on Windows, but should be easy to adapt to Linux. A built for Windows compiled against 8.4.13 with stubs enabled can be downloaded from the URL above.

MJ -- When calling readline::readline, no event processing in the eventloop will take place. This is something to keep in mind when including this in GUI apps. It can be worked around by executing readline in a different thread from everything else. See [L1 ].

See also

Discussion

Mike 2007-11-15:

Getting tclreadline (2.1.0) running on Mac OSX is a hassle. After running "./configure --enable-tclshrl" followed by "make", i got errors about "Undefined symbols ...". To fix it you need to do the following:

  • After you have made sure you have a recent version of the readline library (I use 5.2), you will need to make sure that the symbolic link under /usr/lib/libreadline.dylib is pointing to the right location (originally it was pointing to /usr/lib/libedit.dylib).

If you enable tclreadline in your tclsh and get an "alloc: invalid block: ..." error, then you will have to do the following:

  • Edit tclreadline.c file and rename all MALLOC to malloc and FREE to free (and remove the macros MALLOC and FREE)

PYL 2007-12-24:

  • Apparently /usr/lib/libedit.dylib is or contains libreadline as a 'nm -o /usr/lib/libedit.dylib' will show you...
  • I still cannot build tclreadline on Mac OSX. Could someone provide a link to some nice build so that I could RTFM ???
  • Build on GNU/linux was a breeze...

Will 2008-03-16:

I just managed to build tcllibreadline on Mac OS 10.4. I first did everything Mike did (note that you will need to have the proper libreadline - I had 5.2 under /usr/local/lib already but I don't think that came with the OS - and check the /usr/lib/libreadline.dylib symlink isn't incorrect). In addition I had to:

  • set the environment variable MACOSX_DEPLOYMENT_TARGET to 10.4 (I used "setenv MACOSX_DEPLOYMENT_TARGET 10.4" but I use tcsh - always forget the bash syntax).
  • link glibtool into the build directory with "ln -s /usr/bin/glibtool libtool" as the configure scripts weren't able to create their own.

Chap 2010-01-20 23:03:18:

Things I did to install libtclreadline-2.1.0 and readline-6.1 on Mac OS X 10.6.2 with TCL 8.5 that came with OSX.

  • built a libreadline.dylib by downloading readline-6.1.tar.gz from http://tiswww.case.edu/php/chet/readline/rltop.html , and following the README. (It's standard procedure: from Terminal, navigate into the readline-6.1 directory, enter './configure', and 'make'.) DON'T 'make install', however.
  • /usr/lib/libreadline.dylib is a symlink to /usr/lib/libedit.dylib. On the recommendation of someone at the Apple Discussions Unix forum, I renamed the symlink to /usr/lib/libreadline_OLD.dylib and moved my newly-built libreadline.dylib (from previous step) into /usr/lib/. Needed sudo, of course.
  • built a libtclreadline.dylib by downloading it from http://tclreadline.sourceforge.net/ . (Migraine sufferers are cautioned about the page's color scheme.) Unfortunately, this package is so old it needs some tweaks to get it to build a dylib:

From etresoft on Apple Discussions forum:

... the tclreadline library uses old versions of autoconf. Before calling "./configure" on this project, run "autoreconf -fvi". That will enable shared libraries.

Quite correct - and there's one more thing to do before building.

If you enable tclreadline in your tclsh and get an "alloc: invalid block: ..." error, then you will have to do the following: Edit tclreadline.c file and rename all MALLOC to malloc and FREE to free (and remove the macros MALLOC and FREE)

Having done that, I then ran './configure', 'make', and 'sudo make install'

-- From tclreadline.sourceforge.net:

If you want to use tclreadline as a line interface for developing tcl scripts, you probably don't have to read much of the following section. Just install the package and put the following lines to your $HOME/.tclshrc:

if {$tcl_interactive} {
    package require tclreadline 
    ::tclreadline::Loop
}

My goal was simply to be able to run 'tclsh -i' and have a modern command line for learning and experimenting with TCL. So I did exactly what's described above.

-- It worked. I hope this helps somebody.


bovine 2010-05-03 16:56:50:

These steps are a modified version of Chap's instructions so that you do not have to replace the system libreadline. Tested on MacOS X 10.6.3

Download these two tarballs:

Run these commands:

cd ~/

tar xvzf ~/Downloads/readline-6.1.tar.gz

tar xvzf ~/Downloads/tclreadline-2.1.0.tar.gz

cd ~/readline-6.1/

./configure --disable-shared --prefix=/tmp/readline-install/

make

make install

cd ~/tclreadline-2.1.0/

autoreconf -fvi

./configure --with-readline-includes=/tmp/readline-install/include/readline --with-readline-library=/tmp/readline-install/lib/libreadline.a

egrep -v '#define (FREE|MALLOC)' tclreadline.c | sed 's/MALLOC/malloc/; s/FREE/free/' > tclreadline.c.new

mv -f tclreadline.c.new tclreadline.c

make

sudo make install

rm -rf /tmp/readline-install

hv: Thank you. I followed bovine's instruction and finally got tclreadline to build, installed, and was able to use it. Great instruction.


Debian/Ubuntu offer tclreadline as standard packages, installable via the package manager. After installing tclreadline, a new file .tclshrc must be created in the home directory with following content (same as above):

if {$tcl_interactive} {
    package require tclreadline
    ::tclreadline::Loop
}

sathwikm91 - 2017-02-10 02:21:55

sath: I tried the above procedure by bovine on my mac OS Sierra Version 10.12.2: I am getting this error when I run 'make install' clang: error: invalid argument '-compatibility_version 6' only allowed with '-dynamiclib' Nevertheless when I continue I am getting this error when I run 'autoreconf -fvi': -bash: autoreconf: command not found. Later when I run just 'make' and 'sudo make install' I am getting this error: libtool: link: cannot build libtool library `libtclreadline.la' from non-libtool objects: /tmp/readline-install/lib/libreadline.a make: *** libtclreadline.la Error 1

Please help. Newbie here.


MrTao - 2017-07-21 14:40:09

I have created a pull request for tclreadline to be included in Homebrew to make interaction with tclsh less painful experience on macOS.


JV - 2024-03-08

I have been using package tcl-tclreadline in Ubuntu, as recommended here, but I just discovered that it makes it impposible to use the boolean operator !. For instance expr {!(3&4)} fails because ! is interpreted as referring to some previous event. To get readline support, it is much simpler it is to use package rlwrapand then rlwrap tclsh or rlwrap wish.