Project

General

Profile

Feature #12659 ยป readline_quoting_detection_proc.patch

georgebrock (George Brocklehurst), 08/06/2016 12:55 AM

View differences:

ext/readline/extconf.rb
81 81
readline.have_var("rl_editing_mode")
82 82
readline.have_var("rl_line_buffer")
83 83
readline.have_var("rl_point")
84
readline.have_var("rl_char_is_quoted_p")
84 85
# workaround for native windows.
85 86
/mswin|bccwin|mingw/ !~ RUBY_PLATFORM && readline.have_var("rl_event_hook")
86 87
/mswin|bccwin|mingw/ !~ RUBY_PLATFORM && readline.have_var("rl_catch_sigwinch")
ext/readline/readline.c
59 59
#define COMPLETION_PROC "completion_proc"
60 60
#define COMPLETION_CASE_FOLD "completion_case_fold"
61 61
static ID completion_proc, completion_case_fold;
62
#if defined HAVE_RL_CHAR_IS_QUOTED_P
63
#define QUOTING_DETECTION_PROC "quoting_detection_proc"
64
static ID quoting_detection_proc;
65
#endif
62 66
#if USE_INSERT_IGNORE_ESCAPE
63 67
static ID id_orig_prompt, id_last_prompt;
64 68
#endif
......
88 92
static char **readline_attempted_completion_function(const char *text,
89 93
                                                     int start, int end);
90 94

  
95
#ifdef HAVE_RL_CHAR_IS_QUOTED_P
96
int readline_char_is_quoted(char *text, int index);
97
long byte_index_to_char_index(VALUE str, long byte_index);
98
#endif
99

  
91 100
#define OutputStringValue(str) do {\
92 101
    SafeStringValue(str);\
93 102
    (str) = rb_str_conv_enc((str), rb_enc_get(str), rb_locale_encoding());\
......
832 841
    return rb_attr_get(mReadline, completion_proc);
833 842
}
834 843

  
844
#ifdef HAVE_RL_CHAR_IS_QUOTED_P
845
/*
846
 * call-seq:
847
 *   Readline.quoting_detection_proc = proc
848
 *
849
 * Specifies a Proc object +proc+ to determine if a character in the user's
850
 * input is escaped. It should take the user's input and the index of the
851
 * character in question as input, and return a boolean (true if the specified
852
 * character is escaped).
853
 *
854
 * Readline will only call this proc with characters specified in
855
 * +completer_quote_characters+, to discover if they indicate the end of a
856
 * quoted argument, or characters specified in
857
 * +completer_word_break_characters+, to discover if they indicate a break
858
 * between arguments.
859
 *
860
 * If +completer_quote_characters+ is not set, or if the user input doesn't
861
 * contain one of the +completer_quote_characters+ or a +\+ character,
862
 * Readline will not attempt to use this proc at all.
863
 *
864
 * Raises ArgumentError if +proc+ does not respond to the call method.
865
 */
866
static VALUE
867
readline_s_set_quoting_detection_proc(VALUE self, VALUE proc)
868
{
869
    if (!NIL_P(proc) && !rb_respond_to(proc, rb_intern("call")))
870
        rb_raise(rb_eArgError, "argument must respond to `call'");
871
    return rb_ivar_set(mReadline, quoting_detection_proc, proc);
872
}
873
#else
874
#define readline_s_set_quoting_detection_proc rb_f_notimplement
875
#endif
876

  
877
#ifdef HAVE_RL_CHAR_IS_QUOTED_P
878
/*
879
 * call-seq:
880
 *   Readline.quoting_detection_proc -> proc
881
 *
882
 * Returns the quoting detection Proc object.
883
 */
884
static VALUE
885
readline_s_get_quoting_detection_proc(VALUE self)
886
{
887
    return rb_attr_get(mReadline, quoting_detection_proc);
888
}
889
#else
890
#define readline_s_get_quoting_detection_proc rb_f_notimplement
891
#endif
892

  
835 893
/*
836 894
 * call-seq:
837 895
 *   Readline.completion_case_fold = bool
......
1007 1065
    return result;
1008 1066
}
1009 1067

  
1068
#ifdef HAVE_RL_CHAR_IS_QUOTED_P
1069
int
1070
readline_char_is_quoted(char *text, int byte_index)
1071
{
1072
    VALUE proc, result, str;
1073
    long char_index;
1074

  
1075
    proc = rb_attr_get(mReadline, quoting_detection_proc);
1076
    if (NIL_P(proc)) {
1077
        return 0;
1078
    }
1079

  
1080
    str = rb_locale_str_new_cstr(text);
1081
    char_index = byte_index_to_char_index(str, (long)byte_index);
1082

  
1083
    if (char_index == -1) {
1084
        rb_raise(rb_eIndexError, "failed to find character at byte index");
1085
    }
1086

  
1087
    result = rb_funcall(proc, rb_intern("call"), 2, str, LONG2FIX(char_index));
1088
    return result ? 1 : 0;
1089
}
1090

  
1091
long
1092
byte_index_to_char_index(VALUE str, long byte_index)
1093
{
1094
    const char *ptr;
1095
    long ci, bi, len, clen;
1096
    rb_encoding *enc;
1097

  
1098
    enc = rb_enc_get(str);
1099
    len = RSTRING_LEN(str);
1100
    ptr = RSTRING_PTR(str);
1101

  
1102
    for (bi = 0, ci = 0; bi < len; bi += clen, ++ci) {
1103
        if (bi == byte_index) {
1104
            return ci;
1105
        }
1106
        clen = rb_enc_mbclen(ptr + bi, ptr + len, enc);
1107
    }
1108

  
1109
    return -1;
1110
}
1111
#endif
1112

  
1010 1113
#ifdef HAVE_RL_SET_SCREEN_SIZE
1011 1114
/*
1012 1115
 * call-seq:
......
1821 1924
#if defined(HAVE_RL_SPECIAL_PREFIXES)
1822 1925
    id_special_prefixes = rb_intern("special_prefixes");
1823 1926
#endif
1927
#if defined HAVE_RL_CHAR_IS_QUOTED_P
1928
    quoting_detection_proc = rb_intern(QUOTING_DETECTION_PROC);
1929
#endif
1824 1930

  
1825 1931
    mReadline = rb_define_module("Readline");
1826 1932
    rb_define_module_function(mReadline, "readline",
......
1833 1939
                               readline_s_set_completion_proc, 1);
1834 1940
    rb_define_singleton_method(mReadline, "completion_proc",
1835 1941
                               readline_s_get_completion_proc, 0);
1942
    rb_define_singleton_method(mReadline, "quoting_detection_proc=",
1943
                               readline_s_set_quoting_detection_proc, 1);
1944
    rb_define_singleton_method(mReadline, "quoting_detection_proc",
1945
                               readline_s_get_quoting_detection_proc, 0);
1836 1946
    rb_define_singleton_method(mReadline, "completion_case_fold=",
1837 1947
                               readline_s_set_completion_case_fold, 1);
1838 1948
    rb_define_singleton_method(mReadline, "completion_case_fold",
......
1981 2091
#if defined(HAVE_RL_PRE_INPUT_HOOK)
1982 2092
    rl_pre_input_hook = (rl_hook_func_t *)readline_pre_input_hook;
1983 2093
#endif
2094
#if defined HAVE_RL_CHAR_IS_QUOTED_P
2095
    rl_char_is_quoted_p = &readline_char_is_quoted;
2096
#endif
1984 2097
#ifdef HAVE_RL_CATCH_SIGNALS
1985 2098
    rl_catch_signals = 0;
1986 2099
#endif
test/readline/test_readline.rb
464 464
    end
465 465
  end if Readline.respond_to?(:refresh_line)
466 466

  
467
  def test_setting_quoting_detection_proc
468
    return unless Readline.respond_to?(:quoting_detection_proc=)
469

  
470
    expected = proc { |text, index| false }
471
    Readline.quoting_detection_proc = expected
472
    assert_equal(expected, Readline.quoting_detection_proc)
473

  
474
    assert_raise(ArgumentError) do
475
      Readline.quoting_detection_proc = "This does not have call method."
476
    end
477
  end
478

  
479
  def test_using_quoting_detection_proc
480
    saved_completer_quote_characters = Readline.completer_quote_characters
481
    saved_completer_word_break_characters = Readline.completer_word_break_characters
482
    return unless Readline.respond_to?(:quoting_detection_proc=)
483

  
484
    passed_text = nil
485
    line = nil
486

  
487
    with_temp_stdio do |stdin, stdout|
488
      replace_stdio(stdin.path, stdout.path) do
489
        Readline.completion_proc = -> (text) do
490
          passed_text = text
491
          ['completion']
492
        end
493
        Readline.completer_quote_characters = '\'"'
494
        Readline.completer_word_break_characters = ' '
495
        Readline.quoting_detection_proc = -> (text, index) do
496
          index > 0 && text[index-1] == '\\'
497
        end
498

  
499
        stdin.write("first second\\ third\t")
500
        stdin.flush
501
        line = Readline.readline('> ', false)
502
      end
503
    end
504

  
505
    assert_equal('second\\ third', passed_text)
506
    assert_equal('first completion', line)
507
  ensure
508
    Readline.completer_quote_characters = saved_completer_quote_characters
509
    Readline.completer_word_break_characters = saved_completer_word_break_characters
510
  end
511

  
512
  def test_using_quoting_detection_proc_with_multibyte_input
513
    saved_completer_quote_characters = Readline.completer_quote_characters
514
    saved_completer_word_break_characters = Readline.completer_word_break_characters
515
    return unless Readline.respond_to?(:quoting_detection_proc=)
516
    unless Encoding.find("locale") == Encoding::UTF_8
517
      return if assert_under_utf8
518
      skip 'this test needs UTF-8 locale'
519
    end
520

  
521
    passed_text = nil
522
    escaped_char_indexes = []
523
    line = nil
524

  
525
    with_temp_stdio do |stdin, stdout|
526
      replace_stdio(stdin.path, stdout.path) do
527
        Readline.completion_proc = -> (text) do
528
          passed_text = text
529
          ['completion']
530
        end
531
        Readline.completer_quote_characters = '\'"'
532
        Readline.completer_word_break_characters = ' '
533
        Readline.quoting_detection_proc = -> (text, index) do
534
          escaped = index > 0 && text[index-1] == '\\'
535
          escaped_char_indexes << index if escaped
536
          escaped
537
        end
538

  
539
        stdin.write("\u3042\u3093 second\\ third\t")
540
        stdin.flush
541
        line = Readline.readline('> ', false)
542
      end
543
    end
544

  
545
    assert_equal([10], escaped_char_indexes)
546
    assert_equal('second\\ third', passed_text)
547
    assert_equal("\u3042\u3093 completion", line)
548
  ensure
549
    Readline.completer_quote_characters = saved_completer_quote_characters
550
    Readline.completer_word_break_characters = saved_completer_word_break_characters
551
  end
552

  
467 553
  private
468 554

  
469 555
  def replace_stdio(stdin_path, stdout_path)