Ticket #14523: trac_14523_readline.patch

File trac_14523_readline.patch, 16.2 KB (added by Volker Braun, 9 years ago)

Updated patch

  • module_list.py

    # HG changeset patch
    # User Volker Braun <vbraun@stp.dias.ie>
    # Date 1371998361 14400
    #      Sun Jun 23 10:39:21 2013 -0400
    # Node ID b3b4d5198c4e606f0de0858f02ad4834d5550530
    # Parent  1735ebddf6d8ea7f338308e8a6d34b69be13235b
    Add interface to readline and clear up signal handling
    
    diff --git a/module_list.py b/module_list.py
    a b  
    700700              depends = [SAGE_INC + '/ratpoints.h'],
    701701              libraries = ["ratpoints", "gmp"]),
    702702
     703    Extension('sage.libs.readline',
     704              sources = ['sage/libs/readline.pyx'],
     705              libraries = ['readline']),
     706
    703707    Extension('sage.libs.singular.singular',
    704708              sources = ['sage/libs/singular/singular.pyx'],
    705709              libraries = singular_libs,
     
    11501154    Extension('sage.misc.lazy_attribute',
    11511155              sources = ['sage/misc/lazy_attribute.pyx']),
    11521156
     1157    Extension('sage.misc.inputhook',
     1158              sources = ['sage/misc/inputhook.pyx']),
     1159
    11531160    Extension('sage.misc.lazy_import',
    11541161              sources = ['sage/misc/lazy_import.pyx']),
    11551162
  • new file sage/libs/readline.pyx

    diff --git a/sage/libs/readline.pyx b/sage/libs/readline.pyx
    new file mode 100644
    - +  
     1"""
     2Readline
     3
     4This is the library behind the command line input, it takes keypresses
     5until you hit Enter and then returns it as a string to Python. We hook
     6into it so we can make it redraw the input area.
     7
     8EXAMPLES::
     9
     10    sage: from sage.libs.readline import *
     11    sage: replace_line('foobar', 0)
     12    sage: set_point(3)
     13    sage: print 'current line:', repr(copy_text(0, get_end()))
     14    current line: 'foobar'
     15    sage: print 'cursor position:', get_point()
     16    cursor position: 3
     17
     18When printing with :class:`interleaved_output` the prompt and current
     19line is removed::
     20
     21    sage: with interleaved_output():
     22    ....:     print 'output'
     23    ....:     print 'current line:', repr(copy_text(0, get_end()))
     24    ....:     print 'cursor position:', get_point()
     25    output
     26    current line: ''
     27    cursor position: 0
     28
     29After the interleaved output, the line and cursor is restored to the
     30old value::
     31
     32    sage: print 'current line:', repr(copy_text(0, get_end()))
     33    current line: 'foobar'
     34    sage: print 'cursor position:', get_point()
     35    cursor position: 3
     36   
     37Finally, clear the current line for the remaining doctests::
     38
     39    sage: replace_line('', 1)
     40"""
     41
     42#*****************************************************************************
     43#       Copyright (C) 2013 Volker Braun  <vbraun.name@gmail.com>
     44#
     45#  Distributed under the terms of the GNU General Public License (GPL)
     46#  as published by the Free Software Foundation; either version 3 of
     47#  the License, or (at youroption) any later version.
     48#                  http://www.gnu.org/licenses/
     49#*****************************************************************************
     50
     51
     52
     53cdef extern from 'readline/readline.h':
     54    int rl_forced_update_display()
     55    int rl_redisplay()
     56    int rl_message(char* msg)
     57    int rl_clear_message()
     58    void rl_replace_line(char* line, int pos)
     59    char* rl_copy_text(int begin, int end)
     60    void rl_save_prompt()
     61    void rl_restore_prompt()
     62    int rl_read_key()
     63    int rl_set_signals()
     64    int rl_clear_signals()
     65    int rl_crlf()
     66    int rl_initialize()
     67    int rl_catch_signals
     68    int rl_catch_sigwinch
     69    int rl_point
     70    int rl_end
     71
     72
     73def print_status():
     74    """
     75    Print readline status for debug purposes
     76
     77    EXAMPLES::
     78
     79        sage: from sage.libs.readline import print_status
     80        sage: print_status()
     81        catch_signals: 1
     82        catch_sigwinch: 1
     83    """
     84    print 'catch_signals:', rl_catch_signals
     85    print 'catch_sigwinch:', rl_catch_sigwinch
     86
     87def set_signals():
     88    """
     89    Install the readline signal handlers
     90
     91    Install Readline's signal handler for SIGINT, SIGQUIT, SIGTERM,
     92    SIGALRM, SIGTSTP, SIGTTIN, SIGTTOU, and SIGWINCH, depending on the
     93    values of rl_catch_signals and rl_catch_sigwinch.
     94
     95    EXAMPLES::
     96
     97        sage: from sage.libs.readline import set_signals
     98        sage: set_signals()
     99        0
     100    """
     101    return rl_set_signals()
     102
     103
     104def clear_signals():
     105    """
     106    Remove the readline signal handlers
     107
     108    Remove all of the Readline signal handlers installed by
     109    :func:`set_signals`
     110
     111    EXAMPLES::
     112
     113        sage: from sage.libs.readline import clear_signals
     114        sage: clear_signals()
     115        0
     116    """
     117    return rl_clear_signals()
     118   
     119def get_point():
     120    """
     121    Get the cursor position
     122
     123    OUTPUT:
     124
     125    Integer
     126
     127    EXAMPLES::
     128
     129        sage: from sage.libs.readline import get_point, set_point
     130        sage: get_point()
     131        0
     132        sage: set_point(5)
     133        sage: get_point()
     134        5
     135        sage: set_point(0)
     136    """
     137    return rl_point
     138
     139def get_end():
     140    """
     141    Get the end position of the current input
     142
     143    OUTPUT:
     144
     145    Integer
     146
     147    EXAMPLES::
     148
     149        sage: from sage.libs.readline import get_end
     150        sage: get_end()
     151        0
     152    """
     153    return rl_end
     154
     155def set_point(point):
     156    """
     157    Set the cursor position
     158
     159    INPUT:
     160
     161    - ``point`` -- integer. The new cursor position.
     162
     163    EXAMPLES::
     164
     165        sage: from sage.libs.readline import get_point, set_point
     166        sage: get_point()
     167        0
     168        sage: set_point(5)
     169        sage: get_point()
     170        5
     171        sage: set_point(0)
     172    """
     173    global rl_point
     174    rl_point = point
     175
     176def forced_update_display():
     177    """
     178    Force the line to be updated and redisplayed, whether or not
     179    Readline thinks the screen display is correct.
     180
     181    EXAMPLES::
     182
     183        sage: from sage.libs.readline import forced_update_display
     184        sage: forced_update_display()
     185        0
     186    """
     187    return rl_forced_update_display()
     188
     189def copy_text(pos_start, pos_end):
     190    """
     191    Return a copy of the text between start and end in the current line.
     192
     193    INPUT:
     194
     195    - ``pos_start``, ``pos_end`` -- integer. Start and end position.
     196
     197    OUTPUT:
     198
     199    String.
     200
     201    EXAMPLES::
     202
     203        sage: from sage.libs.readline import copy_text, replace_line
     204        sage: replace_line('foobar', 0)
     205        sage: copy_text(1, 5)
     206        'ooba'
     207    """
     208    return rl_copy_text(pos_start, pos_end)
     209
     210def replace_line(text, clear_undo):
     211    """
     212    Replace the contents of rl_line_buffer with text.
     213
     214    The point and mark are preserved, if possible.
     215
     216    INPUT:
     217
     218    - ``text`` -- the new content of the line.
     219
     220    - ``clear_undo`` -- integer. If non-zero, the undo list associated
     221      with the current line is cleared.
     222     
     223    EXAMPLES::
     224
     225        sage: from sage.libs.readline import copy_text, replace_line
     226        sage: replace_line('foobar', 0)
     227        sage: copy_text(1, 5)
     228        'ooba'
     229    """
     230    rl_replace_line(text, clear_undo)
     231
     232def initialize():
     233    """
     234    Initialize or re-initialize Readline’s internal state. It’s not
     235    strictly necessary to call this; readline() calls it before
     236    reading any input.
     237
     238    EXAMPLES::
     239
     240        sage: from sage.libs.readline import initialize
     241        sage: initialize()
     242        0
     243    """
     244    return rl_initialize()
     245
     246
     247
     248class interleaved_output:
     249
     250    def __init__(self):
     251        """
     252        Context manager for asynchronous output
     253
     254        This allows you to show output while at the readline
     255        prompt. When the block is left, the prompt is restored even if
     256        it was clobbered by the output.
     257
     258        EXAMPLES::
     259
     260            sage: from sage.libs.readline import interleaved_output
     261            sage: with interleaved_output():
     262            ....:     print 'output'
     263            output
     264        """
     265        pass
     266
     267    def __enter__(self):
     268        """
     269        Called when entering the with block
     270
     271        EXAMPLES::
     272
     273            sage: from sage.libs.readline import interleaved_output
     274            sage: with interleaved_output():
     275            ....:     print 'output'
     276            output
     277        """
     278        self._saved_point = rl_point;
     279        self._saved_line = rl_copy_text(0, rl_end)
     280        rl_save_prompt()
     281        rl_replace_line('', 0)
     282        rl_redisplay()
     283        rl_clear_signals()
     284
     285    def __exit__(self, exc_type, exc_val, exc_tb):
     286        """
     287        Called when entering the with block
     288
     289        EXAMPLES::
     290
     291            sage: from sage.libs.readline import interleaved_output
     292            sage: with interleaved_output():
     293            ....:     print 'output'
     294            output
     295        """
     296        rl_set_signals()
     297        rl_replace_line(self._saved_line, 0)
     298        global rl_point
     299        rl_point = self._saved_point
     300        rl_restore_prompt()
     301        rl_forced_update_display()
     302        return False
     303   
     304
     305
     306
     307
  • sage/misc/attached_files.py

    diff --git a/sage/misc/attached_files.py b/sage/misc/attached_files.py
    a b  
    577577    for filename, mtime in modified_file_iterator():
    578578        basename = os.path.basename(filename)
    579579        timestr = time.strftime('%T', mtime)
    580         print '### reloading attached file {0} modified at {1} ###'.format(basename, timestr)
    581         code = load_wrap(filename, attach=True)
    582         get_ipython().run_cell(code)
     580        from sage.libs.readline import interleaved_output
     581        with interleaved_output():
     582            print '### reloading attached file {0} modified at {1} ###'.format(basename, timestr)
     583            code = load_wrap(filename, attach=True)
     584            get_ipython().run_cell(code)
  • deleted file sage/misc/inputhook.py

    diff --git a/sage/misc/inputhook.py b/sage/misc/inputhook.py
    deleted file mode 100644
    + -  
    1 """
    2 The Sage Input Hook
    3 
    4 This is a hook into the IPython input prompt and will be called
    5 periodically (every 100ms) while Python is sitting idle. We use it to
    6 reload attached files if they have changed.
    7 """
    8 
    9 ###########################################################################
    10 #       Copyright (C) 2013 Volker Braun <vbraun.name@gmail.com>
    11 #
    12 #  Distributed under the terms of the GNU General Public License (GPL)
    13 #                  http://www.gnu.org/licenses/
    14 ###########################################################################
    15 
    16 
    17 from sage.misc.attached_files import reload_attached_files_if_modified
    18 
    19 
    20 def sage_inputhook():
    21     """
    22     The input hook.
    23 
    24     This function will be called every 100ms when IPython is idle at
    25     the command prompt.
    26    
    27     EXAMPLES::
    28    
    29         sage: from sage.misc.interpreter import get_test_shell
    30         sage: shell = get_test_shell()
    31         sage: tmp = tmp_filename(ext='.py')
    32         sage: f = open(tmp, 'w'); f.write('a = 2\n'); f.close()
    33         sage: shell.run_cell('%attach ' + tmp)
    34         sage: shell.run_cell('a')
    35         2
    36         sage: f = open(tmp, 'w'); f.write('a = 3\n'); f.close()
    37 
    38     Note that the doctests are never really at the command prompt, so
    39     we call the input hook manually::
    40    
    41         sage: shell.run_cell('from sage.misc.inputhook import sage_inputhook')
    42         sage: shell.run_cell('sage_inputhook()')
    43         ### reloading attached file tmp_....py modified at ... ###
    44         0
    45 
    46         sage: shell.run_cell('a')
    47         3
    48         sage: shell.run_cell('detach({0})'.format(repr(tmp)))
    49         sage: shell.run_cell('attached_files()')
    50         []
    51     """
    52     reload_attached_files_if_modified()
    53     return 0
    54    
    55    
    56        
    57        
    58    
  • new file sage/misc/inputhook.pyx

    diff --git a/sage/misc/inputhook.pyx b/sage/misc/inputhook.pyx
    new file mode 100644
    - +  
     1"""
     2The Sage Input Hook
     3
     4This is a hook into the IPython input prompt and will be called
     5periodically (every 100ms) while Python is sitting idle. We use it to
     6reload attached files if they have changed.
     7
     8IPython has analogous code to set an input hook, but we are not using
     9their implementation. For once, it unsets signal handlers which will
     10disable Ctrl-C.
     11"""
     12
     13###########################################################################
     14#       Copyright (C) 2013 Volker Braun <vbraun.name@gmail.com>
     15#
     16#  Distributed under the terms of the GNU General Public License (GPL)
     17#                  http://www.gnu.org/licenses/
     18###########################################################################
     19
     20include 'sage/ext/stdsage.pxi'
     21include 'sage/ext/interrupt.pxi'
     22
     23cdef extern from 'pythonrun.h':
     24    int (*PyOS_InputHook)() nogil except *
     25
     26import sage.libs.readline as readline
     27from sage.misc.attached_files import reload_attached_files_if_modified
     28
     29
     30cdef int c_sage_inputhook() nogil except *:
     31    """
     32    This is the C function that is installed as PyOS_InputHook
     33    """
     34    with gil:
     35        try:
     36            sig_check()
     37            return sage_inputhook()
     38        except KeyboardInterrupt:
     39            # The user pressed Ctrl-C while at the prompt; We match the normal
     40            # Python behavior for consistency
     41            print '\nKeyboardInterrupt'
     42            readline.initialize()
     43            readline.forced_update_display()
     44        return 0
     45
     46def install():
     47    """
     48    Install the Sage input hook
     49   
     50    EXAMPLES::
     51
     52        sage: from sage.misc.inputhook import install
     53        sage: install()
     54    """
     55    global PyOS_InputHook
     56    PyOS_InputHook = c_sage_inputhook
     57
     58def uninstall():
     59    """
     60    Uninstall the Sage input hook
     61
     62    EXAMPLES::
     63
     64        sage: from sage.misc.inputhook import uninstall
     65        sage: uninstall()
     66    """
     67    global PyOS_InputHook
     68    PyOS_InputHook = NULL
     69
     70
     71def sage_inputhook():
     72    """
     73    The input hook.
     74
     75    This function will be called every 100ms when IPython is idle at
     76    the command prompt.
     77   
     78    EXAMPLES::
     79   
     80        sage: from sage.misc.interpreter import get_test_shell
     81        sage: shell = get_test_shell()
     82        sage: tmp = tmp_filename(ext='.py')
     83        sage: f = open(tmp, 'w'); f.write('a = 2\n'); f.close()
     84        sage: shell.run_cell('%attach ' + tmp)
     85        sage: shell.run_cell('a')
     86        2
     87        sage: sleep(1)  # filesystem timestamp granularity
     88        sage: f = open(tmp, 'w'); f.write('a = 3\n'); f.close()
     89
     90    Note that the doctests are never really at the command prompt, so
     91    we call the input hook manually::
     92   
     93        sage: shell.run_cell('from sage.misc.inputhook import sage_inputhook')
     94        sage: shell.run_cell('sage_inputhook()')
     95        ### reloading attached file tmp_....py modified at ... ###
     96        0
     97
     98        sage: shell.run_cell('a')
     99        3
     100        sage: shell.run_cell('detach({0})'.format(repr(tmp)))
     101        sage: shell.run_cell('attached_files()')
     102        []
     103    """
     104    reload_attached_files_if_modified()
     105    return 0
     106   
     107   
     108       
     109       
     110   
  • sage/misc/sage_extension.py

    diff --git a/sage/misc/sage_extension.py b/sage/misc/sage_extension.py
    a b  
    9898            sage: shell.run_cell('%attach ' + tmp)
    9999            sage: shell.run_cell('a')
    100100            2
     101            sage: sleep(1)  # filesystem timestamp granularity
    101102            sage: f = open(tmp, 'w'); f.write('a = 3\n'); f.close()
    102103
    103104        Note that the doctests are never really at the command prompt, so
     
    401402        self.init_inspector()
    402403        self.init_line_transforms()
    403404        self.register_interface_magics()
    404         from sage.misc.inputhook import sage_inputhook
    405         from IPython.lib import inputhook
    406         inputhook.set_inputhook(sage_inputhook)
     405
     406        import sage.misc.inputhook
     407        sage.misc.inputhook.install()
    407408
    408409        # right now, the shutdown hook calling quit_sage() doesn't
    409410        # work when we run doctests that involve creating test shells.
  • sage/misc/sageinspect.py

    diff --git a/sage/misc/sageinspect.py b/sage/misc/sageinspect.py
    a b  
    5454
    5555Python classes::
    5656
    57     sage: import sage.misc.attach
    58     sage: sage_getfile(sage.misc.attach.Attach)
    59     '.../attach.py'
     57    sage: sage_getfile(BlockFinder)
     58    '.../sage/misc/sageinspect.py'
    6059
    61     sage: sage_getdoc(sage.misc.attach.Attach).lstrip()
    62     'Attach a file to a running instance of Sage...'
     60    sage: sage_getdoc(BlockFinder).lstrip()
     61    'Provide a tokeneater() method to detect the...'
    6362
    64     sage: sage_getsource(sage.misc.attach.Attach)
    65     'class Attach:...'
     63    sage: sage_getsource(BlockFinder)
     64    'class BlockFinder:...'
    6665
    6766Python classes with no docstring, but an __init__ docstring::
    6867