1#!/usr/bin/env ruby 2# 3# TkTextIO class :: handling I/O stream on a TkText widget 4# by Hidetoshi NAGAI (nagai@ai.kyutech.ac.jp) 5# 6# NOTE: TkTextIO supports 'character' (not 'byte') access only. 7# So, for example, TkTextIO#getc returns a character, TkTextIO#pos 8# means the character position, TkTextIO#read(size) counts by 9# characters, and so on. 10# Of course, it is available to make TkTextIO class to suuport 11# 'byte' access. However, it may break multi-byte characters. 12# and then, displayed string on the text widget may be garbled. 13# I think that it is not good on the supposed situation of using 14# TkTextIO. 15# 16require 'tk' 17require 'tk/text' 18require 'tk/textmark' 19require 'thread' 20 21class TkTextIO < TkText 22 # keep safe level 23 @@create_queues = proc{ [Queue.new, Mutex.new, Queue.new, Mutex.new] } 24 25 OPT_DEFAULTS = { 26 'mode' => nil, 27 'overwrite' => false, 28 'text' => nil, 29 'show' => :pos, 30 'wrap' => 'char', 31 'sync' => true, 32 'prompt' => nil, 33 'prompt_cmd' => nil, 34 'hist_size' => 1000, 35 } 36 37 def create_self(keys) 38 opts = _get_io_params((keys.kind_of?(Hash))? keys: {}) 39 40 super(keys) 41 42 @count_var = TkVariable.new 43 44 @write_buffer = '' 45 @read_buffer = '' 46 @buf_size = 0 47 @buf_max = 1024 48 49 @write_buf_queue, @write_buf_mutex, 50 @read_buf_queue, @read_buf_mutex = @@create_queues.call 51 52 @idle_flush = TkTimer.new(:idle, 1, proc{ @flusher.run rescue nil }) 53 @timer_flush = TkTimer.new(250, -1, proc{ @flusher.run rescue nil }) 54 55 @flusher = Thread.new{ loop { Thread.stop; flush() } } 56 57 @receiver = Thread.new{ 58 begin 59 loop { 60 str = @write_buf_queue.deq 61 @write_buf_mutex.synchronize { @write_buffer << str } 62 @idle_flush.start 63 } 64 ensure 65 @flusher.kill 66 end 67 } 68 69 @timer_flush.start 70 71 _setup_io(opts) 72 end 73 private :create_self 74 75 def destroy 76 @flusher.kill rescue nil 77 78 @idle_flush.stop rescue nil 79 @timer_flush.stop rescue nil 80 81 @receiver.kill rescue nil 82 83 super() 84 end 85 86 #################################### 87 88 def _get_io_params(keys) 89 opts = {} 90 self.class.const_get(:OPT_DEFAULTS).each{|k, v| 91 if keys.has_key?(k) 92 opts[k] = keys.delete(k) 93 else 94 opts[k] = v 95 end 96 } 97 opts 98 end 99 100 def _setup_io(opts) 101 unless defined? @txtpos 102 @txtpos = TkTextMark.new(self, '1.0') 103 else 104 @txtpos.set('1.0') 105 end 106 @txtpos.gravity = :left 107 108 @lineno = 0 109 @line_offset = 0 110 111 @hist_max = opts['hist_size'].to_i 112 @hist_index = 0 113 @history = Array.new(@hist_max) 114 @history[0] = '' 115 116 self['wrap'] = wrap 117 118 self.show_mode = opts['show'] 119 120 self.value = opts['text'] if opts['text'] 121 122 @overwrite = (opts['overwrite'])? true: false 123 124 @sync = opts['sync'] 125 126 @prompt = opts['prompt'] 127 @prompt_cmd = opts['prompt_cmd'] 128 129 @open = {:r => true, :w => true} # default is 'r+' 130 131 @console_mode = false 132 @end_of_stream = false 133 @console_buffer = nil 134 135 case opts['mode'] 136 when nil 137 # do nothing 138 139 when :console, 'console' 140 @console_mode = true 141 # @console_buffer = TkTextIO.new(:mode=>'r') 142 @console_buffer = self.class.new(:mode=>'r') 143 self.show_mode = :insert 144 145 when 'r', 'rb' 146 @open[:r] = true; @open[:w] = nil 147 148 when 'r+', 'rb+', 'r+b' 149 @open[:r] = true; @open[:w] = true 150 151 when 'w', 'wb' 152 @open[:r] = nil; @open[:w] = true 153 self.value='' 154 155 when 'w+', 'wb+', 'w+b' 156 @open[:r] = true; @open[:w] = true 157 self.value='' 158 159 when 'a', 'ab' 160 @open[:r] = nil; @open[:w] = true 161 @txtpos.set('end - 1 char') 162 @txtpos.gravity = :right 163 164 when 'a+', 'ab+', 'a+b' 165 @open[:r] = true; @open[:w] = true 166 @txtpos.set('end - 1 char') 167 @txtpos.gravity = :right 168 169 else 170 fail ArgumentError, "unknown mode `#{opts['mode']}'" 171 end 172 173 unless defined? @ins_head 174 @ins_head = TkTextMark.new(self, 'insert') 175 @ins_head.gravity = :left 176 end 177 178 unless defined? @ins_tail 179 @ins_tail = TkTextMark.new(self, 'insert') 180 @ins_tail.gravity = :right 181 end 182 183 unless defined? @tmp_mark 184 @tmp_mark = TkTextMark.new(self, 'insert') 185 @tmp_mark.gravity = :left 186 end 187 188 if @console_mode 189 _set_console_line 190 _setup_console_bindings 191 end 192 end 193 private :_get_io_params, :_setup_io 194 195 def _set_console_line 196 @tmp_mark.set(@ins_tail) 197 198 mark_set('insert', 'end') 199 200 prompt = '' 201 prompt << @prompt_cmd.call if @prompt_cmd 202 prompt << @prompt if @prompt 203 insert(@tmp_mark, prompt) 204 205 @ins_head.set(@ins_tail) 206 @ins_tail.set('insert') 207 208 @txtpos.set(@tmp_mark) 209 210 _see_pos 211 end 212 213 def _replace_console_line(str) 214 self.delete(@ins_head, @ins_tail) 215 self.insert(@ins_head, str) 216 end 217 218 def _get_console_line 219 @tmp_mark.set(@ins_tail) 220 s = self.get(@ins_head, @tmp_mark) 221 _set_console_line 222 s 223 end 224 private :_set_console_line, :_replace_console_line, :_get_console_line 225 226 def _cb_up 227 @history[@hist_index].replace(self.get(@ins_head, @ins_tail)) 228 @hist_index += 1 229 @hist_index -= 1 if @hist_index >= @hist_max || !@history[@hist_index] 230 _replace_console_line(@history[@hist_index]) if @history[@hist_index] 231 Tk.callback_break 232 end 233 def _cb_down 234 @history[@hist_index].replace(self.get(@ins_head, @ins_tail)) 235 @hist_index -= 1 236 @hist_index = 0 if @hist_index < 0 237 _replace_console_line(@history[@hist_index]) if @history[@hist_index] 238 Tk.callback_break 239 end 240 def _cb_left 241 if @console_mode && compare('insert', '<=', @ins_head) 242 mark_set('insert', @ins_head) 243 Tk.callback_break 244 end 245 end 246 def _cb_backspace 247 if @console_mode && compare('insert', '<=', @ins_head) 248 Tk.callback_break 249 end 250 end 251 def _cb_ctrl_a 252 if @console_mode 253 mark_set('insert', @ins_head) 254 Tk.callback_break 255 end 256 end 257 def _cb_ctrl_u 258 if @console_mode 259 mark_set('insert', @ins_head) 260 delete('insert', 'insert lineend') 261 Tk.callback_break 262 end 263 end 264 private :_cb_up, :_cb_down, :_cb_left, :_cb_backspace, 265 :_cb_ctrl_a, :_cb_ctrl_u 266 267 def _setup_console_bindings 268 @bindtag = TkBindTag.new 269 270 tags = self.bindtags 271 tags[tags.index(self)+1, 0] = @bindtag 272 self.bindtags = tags 273 274 @bindtag.bind('Return'){ 275 insert('end - 1 char', "\n") 276 if (str = _get_console_line) 277 @read_buf_queue.push(str) 278 279 @history[0].replace(str.chomp) 280 @history.pop 281 @history.unshift('') 282 @hist_index = 0 283 end 284 285 Tk.update 286 Tk.callback_break 287 } 288 @bindtag.bind('Alt-Return'){ 289 Tk.callback_continue 290 } 291 292 @bindtag.bind('FocusIn'){ 293 if @console_mode 294 mark_set('insert', @ins_tail) 295 Tk.callback_break 296 end 297 } 298 299 ins_mark = TkTextMark.new(self, 'insert') 300 301 @bindtag.bind('ButtonPress'){ 302 if @console_mode 303 ins_mark.set('insert') 304 end 305 } 306 307 @bindtag.bind('ButtonRelease-1'){ 308 if @console_mode && compare('insert', '<=', @ins_head) 309 mark_set('insert', ins_mark) 310 Tk.callback_break 311 end 312 } 313 314 @bindtag.bind('ButtonRelease-2', '%x %y'){|x, y| 315 if @console_mode 316 # paste a text at 'insert' only 317 x1, y1, x2, y2 = bbox(ins_mark) 318 unless x == x1 && y == y1 319 Tk.event_generate(self, 'ButtonRelease-2', :x=>x1, :y=>y1) 320 Tk.callback_break 321 end 322 end 323 } 324 325 @bindtag.bind('Up'){ _cb_up } 326 @bindtag.bind('Control-p'){ _cb_up } 327 328 @bindtag.bind('Down'){ _cb_down } 329 @bindtag.bind('Control-n'){ _cb_down } 330 331 @bindtag.bind('Left'){ _cb_left } 332 @bindtag.bind('Control-b'){ _cb_left } 333 334 @bindtag.bind('BackSpace'){ _cb_backspace } 335 @bindtag.bind('Control-h'){ _cb_backspace } 336 337 @bindtag.bind('Home'){ _cb_ctrl_a } 338 @bindtag.bind('Control-a'){ _cb_ctrl_a } 339 340 @bindtag.bind('Control-u'){ _cb_ctrl_u } 341 end 342 private :_setup_console_bindings 343 344 def _block_read(size = nil, ret = '', block_mode = true) 345 return '' if size == 0 346 return nil if ! @read_buf_queue && @read_buffer.empty? 347 ret = '' unless ret.kind_of?(String) 348 ret.replace('') unless ret.empty? 349 350 if block_mode == nil # partial 351 if @read_buffer.empty? 352 ret << @read_buffer.slice!(0..-1) 353 return ret 354 end 355 end 356 357 if size.kind_of?(Numeric) 358 loop{ 359 @read_buf_mutex.synchronize { 360 buf_len = @read_buffer.length 361 if buf_len >= size 362 ret << @read_buffer.slice!(0, size) 363 return ret 364 else 365 ret << @read_buffer.slice!(0..-1) 366 size -= buf_len 367 return ret unless @read_buf_queue 368 end 369 } 370 @read_buffer << @read_buf_queue.pop 371 } 372 else # readline 373 rs = (size)? size: $/ 374 rs = rs.to_s if rs.kind_of?(Regexp) 375 loop{ 376 @read_buf_mutex.synchronize { 377 if (str = @read_buffer.slice!(/\A(.*)(#{rs})/m)) 378 ret << str 379 return ret 380 else 381 ret << @read_buffer.slice!(0..-1) 382 return ret unless @read_buf_queue 383 end 384 } 385 @read_buffer << @read_buf_queue.pop 386 } 387 end 388 end 389 390 def _block_write 391 ###### currently, not support 392 end 393 private :_block_read, :_block_write 394 395 #################################### 396 397 def <<(obj) 398 _write(obj) 399 self 400 end 401 402 def binmode 403 self 404 end 405 406 def clone 407 fail NotImplementedError, 'cannot clone TkTextIO' 408 end 409 def dup 410 fail NotImplementedError, 'cannot duplicate TkTextIO' 411 end 412 413 def close 414 close_read 415 close_write 416 nil 417 end 418 def close_read 419 @open[:r] = false if @open[:r] 420 nil 421 end 422 def close_write 423 @open[:w] = false if @opne[:w] 424 nil 425 end 426 427 def closed?(dir=nil) 428 case dir 429 when :r, 'r' 430 !@open[:r] 431 when :w, 'w' 432 !@open[:w] 433 else 434 !@open[:r] && !@open[:w] 435 end 436 end 437 438 def _check_readable 439 fail IOError, "not opened for reading" if @open[:r].nil? 440 fail IOError, "closed stream" if !@open[:r] 441 end 442 def _check_writable 443 fail IOError, "not opened for writing" if @open[:w].nil? 444 fail IOError, "closed stream" if !@open[:w] 445 end 446 private :_check_readable, :_check_writable 447 448 def each_line(rs = $/) 449 _check_readable 450 while(s = self.gets(rs)) 451 yield(s) 452 end 453 self 454 end 455 alias each each_line 456 457 def each_char 458 _check_readable 459 while(c = self.getc) 460 yield(c) 461 end 462 self 463 end 464 alias each_byte each_char 465 466 def eof? 467 compare(@txtpos, '==', 'end - 1 char') 468 end 469 alias eof eof? 470 471 def fcntl(*args) 472 fail NotImplementedError, "fcntl is not implemented on #{self.class}" 473 end 474 475 def fsync 476 0 477 end 478 479 def fileno 480 nil 481 end 482 483 def flush 484 Thread.pass 485 if @open[:w] || ! @write_buffer.empty? 486 @write_buf_mutex.synchronize { 487 _sync_write_buf(@write_buffer) 488 @write_buffer[0..-1] = '' 489 } 490 end 491 self 492 end 493 494 def getc 495 return _block_read(1) if @console_mode 496 497 _check_readable 498 return nil if eof? 499 c = get(@txtpos) 500 @txtpos.set(@txtpos + '1 char') 501 _see_pos 502 c 503 end 504 505 def gets(rs = $/) 506 return _block_read(rs) if @console_mode 507 508 _check_readable 509 return nil if eof? 510 _readline(rs) 511 end 512 513 def ioctrl(*args) 514 fail NotImplementedError, 'iocntl is not implemented on TkTextIO' 515 end 516 517 def isatty 518 false 519 end 520 def tty? 521 false 522 end 523 524 def lineno 525 @lineno + @line_offset 526 end 527 528 def lineno=(num) 529 @line_offset = num - @lineno 530 num 531 end 532 533 def overwrite? 534 @overwrite 535 end 536 537 def overwrite=(ovwt) 538 @overwrite = (ovwt)? true: false 539 end 540 541 def pid 542 nil 543 end 544 545 def index_pos 546 index(@txtpos) 547 end 548 alias tell_index index_pos 549 550 def index_pos=(idx) 551 @txtpos.set(idx) 552 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 553 _see_pos 554 idx 555 end 556 557 def pos 558 s = get('1.0', @txtpos) 559 number(tk_call('string', 'length', s)) 560 end 561 alias tell pos 562 563 def pos=(idx) 564 seek(idx, IO::SEEK_SET) 565 idx 566 end 567 568 def pos_gravity 569 @txtpos.gravity 570 end 571 572 def pos_gravity=(side) 573 @txtpos.gravity = side 574 side 575 end 576 577 def print(arg=$_, *args) 578 _check_writable 579 args.unshift(arg) 580 args.map!{|val| (val == nil)? 'nil': val.to_s } 581 str = args.join($,) 582 str << $\ if $\ 583 _write(str) 584 nil 585 end 586 def printf(*args) 587 _check_writable 588 _write(sprintf(*args)) 589 nil 590 end 591 592 def putc(c) 593 _check_writable 594 c = c.chr if c.kind_of?(Fixnum) 595 _write(c) 596 c 597 end 598 599 def puts(*args) 600 _check_writable 601 if args.empty? 602 _write("\n") 603 return nil 604 end 605 args.each{|arg| 606 if arg == nil 607 _write("nil\n") 608 elsif arg.kind_of?(Array) 609 puts(*arg) 610 elsif arg.kind_of?(String) 611 _write(arg.chomp) 612 _write("\n") 613 else 614 begin 615 arg = arg.to_ary 616 puts(*arg) 617 rescue 618 puts(arg.to_s) 619 end 620 end 621 } 622 nil 623 end 624 625 def _read(len) 626 epos = @txtpos + "#{len} char" 627 s = get(@txtpos, epos) 628 @txtpos.set(epos) 629 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 630 _see_pos 631 s 632 end 633 private :_read 634 635 def read(len=nil, buf=nil) 636 return _block_read(len, buf) if @console_mode 637 638 _check_readable 639 if len 640 return "" if len == 0 641 return nil if eof? 642 s = _read(len) 643 else 644 s = get(@txtpos, 'end - 1 char') 645 @txtpos.set('end - 1 char') 646 _see_pos 647 end 648 buf.replace(s) if buf.kind_of?(String) 649 s 650 end 651 652 def readchar 653 return _block_read(1) if @console_mode 654 655 _check_readable 656 fail EOFError if eof? 657 c = get(@txtpos) 658 @txtpos.set(@txtpos + '1 char') 659 _see_pos 660 c 661 end 662 663 def _readline(rs = $/) 664 if rs == nil 665 s = get(@txtpos, 'end - 1 char') 666 @txtpos.set('end - 1 char') 667 elsif rs == '' 668 @count_var.value # make it global 669 idx = tksearch_with_count([:regexp], @count_var, 670 "\n(\n)+", @txtpos, 'end - 1 char') 671 if idx 672 s = get(@txtpos, idx) << "\n" 673 @txtpos.set("#{idx} + #{@count_var.value} char") 674 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 675 else 676 s = get(@txtpos, 'end - 1 char') 677 @txtpos.set('end - 1 char') 678 end 679 else 680 @count_var.value # make it global 681 idx = tksearch_with_count(@count_var, rs, @txtpos, 'end - 1 char') 682 if idx 683 s = get(@txtpos, "#{idx} + #{@count_var.value} char") 684 @txtpos.set("#{idx} + #{@count_var.value} char") 685 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 686 else 687 s = get(@txtpos, 'end - 1 char') 688 @txtpos.set('end - 1 char') 689 end 690 end 691 692 _see_pos 693 @lineno += 1 694 $_ = s 695 end 696 private :_readline 697 698 def readline(rs = $/) 699 return _block_readline(rs) if @console_mode 700 701 _check_readable 702 fail EOFError if eof? 703 _readline(rs) 704 end 705 706 def readlines(rs = $/) 707 if @console_mode 708 lines = [] 709 while (line = _block_readline(rs)) 710 lines << line 711 end 712 return lines 713 end 714 715 _check_readable 716 lines = [] 717 until(eof?) 718 lines << _readline(rs) 719 end 720 $_ = nil 721 lines 722 end 723 724 def readpartial(maxlen, buf=nil) 725 #return @console_buffer.readpartial(maxlen, buf) if @console_mode 726 return _block_read(maxlen, buf, nil) if @console_mode 727 728 _check_readable 729 fail EOFError if eof? 730 s = _read(maxlen) 731 buf.replace(s) if buf.kind_of?(String) 732 s 733 end 734 735 def reopen(*args) 736 fail NotImplementedError, 'reopen is not implemented on TkTextIO' 737 end 738 739 def rewind 740 @txtpos.set('1.0') 741 _see_pos 742 @lineno = 0 743 @line_offset = 0 744 self 745 end 746 747 def seek(offset, whence=IO::SEEK_SET) 748 case whence 749 when IO::SEEK_SET 750 offset = "1.0 + #{offset} char" if offset.kind_of?(Numeric) 751 @txtpos.set(offset) 752 753 when IO::SEEK_CUR 754 offset = "#{offset} char" if offset.kind_of?(Numeric) 755 @txtpos.set(@txtpos + offset) 756 757 when IO::SEEK_END 758 offset = "#{offset} char" if offset.kind_of?(Numeric) 759 @txtpos.set("end - 1 char + #{offset}") 760 761 else 762 fail Errno::EINVAL, 'invalid whence argument' 763 end 764 765 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 766 _see_pos 767 768 0 769 end 770 alias sysseek seek 771 772 def _see_pos 773 see(@show) if @show 774 end 775 private :_see_pos 776 777 def show_mode 778 (@show == @txtpos)? :pos : @show 779 end 780 781 def show_mode=(mode) 782 # define show mode when file position is changed. 783 # mode == :pos or "pos" or true :: see current file position. 784 # mode == :insert or "insert" :: see insert cursor position. 785 # mode == nil or false :: do nothing 786 # else see 'mode' position ('mode' should be text index or mark) 787 case mode 788 when :pos, 'pos', true 789 @show = @txtpos 790 when :insert, 'insert' 791 @show = :insert 792 when nil, false 793 @show = false 794 else 795 begin 796 index(mode) 797 rescue 798 fail ArgumentError, 'invalid show-position' 799 end 800 @show = mode 801 end 802 803 _see_pos 804 805 mode 806 end 807 808 def stat 809 fail NotImplementedError, 'stat is not implemented on TkTextIO' 810 end 811 812 def sync 813 @sync 814 end 815 816 def sync=(mode) 817 @sync = mode 818 end 819 820 def sysread(len, buf=nil) 821 return _block_read(len, buf) if @console_mode 822 823 _check_readable 824 fail EOFError if eof? 825 s = _read(len) 826 buf.replace(s) if buf.kind_of?(String) 827 s 828 end 829 830 def syswrite(obj) 831 _write(obj) 832 end 833 834 def to_io 835 self 836 end 837 838 def trancate(len) 839 delete("1.0 + #{len} char", :end) 840 0 841 end 842 843 def ungetc(c) 844 if @console_mode 845 @read_buf_mutex.synchronize { 846 @read_buffer[0,0] = c.chr 847 } 848 return nil 849 end 850 851 _check_readable 852 c = c.chr if c.kind_of?(Fixnum) 853 if compare(@txtpos, '>', '1.0') 854 @txtpos.set(@txtpos - '1 char') 855 delete(@txtpos) 856 insert(@txtpos, tk_call('string', 'range', c, 0, 1)) 857 @txtpos.set(@txtpos - '1 char') if @txtpos.gravity == 'right' 858 _see_pos 859 else 860 fail IOError, 'cannot ungetc at head of stream' 861 end 862 nil 863 end 864 865=begin 866 def _write(obj) 867 #s = _get_eval_string(obj) 868 s = (obj.kind_of?(String))? obj: obj.to_s 869 n = number(tk_call('string', 'length', s)) 870 delete(@txtpos, @txtpos + "#{n} char") if @overwrite 871 self.insert(@txtpos, s) 872 @txtpos.set(@txtpos + "#{n} char") 873 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 874 _see_pos 875 Tk.update if @sync 876 n 877 end 878 private :_write 879=end 880#=begin 881 def _sync_write_buf(s) 882 if (n = number(tk_call('string', 'length', s))) > 0 883 delete(@txtpos, @txtpos + "#{n} char") if @overwrite 884 self.insert(@txtpos, s) 885 #Tk.update 886 887 @txtpos.set(@txtpos + "#{n} char") 888 @txtpos.set('end - 1 char') if compare(@txtpos, '>=', :end) 889 890 @ins_head.set(@txtpos) if compare(@txtpos, '>', @ins_head) 891 892 _see_pos 893 end 894 self 895 end 896 private :_sync_write_buf 897 898 def _write(obj) 899 s = (obj.kind_of?(String))? obj: obj.to_s 900 n = number(tk_call('string', 'length', s)) 901 @write_buf_queue.enq(s) 902 if @sync 903 Thread.pass 904 Tk.update 905 end 906 n 907 end 908 private :_write 909#=end 910 911 def write(obj) 912 _check_writable 913 _write(obj) 914 end 915end 916 917#################### 918# TEST 919#################### 920if __FILE__ == $0 921 ev_loop = Thread.new{Tk.mainloop} 922 923 f = TkFrame.new.pack 924 #tio = TkTextIO.new(f, :show=>:nil, 925 #tio = TkTextIO.new(f, :show=>:pos, 926 tio = TkTextIO.new(f, :show=>:insert, 927 :text=>">>> This is an initial text line. <<<\n\n"){ 928# yscrollbar(TkScrollbar.new(f).pack(:side=>:right, :fill=>:y)) 929 pack(:side=>:left, :fill=>:both, :expand=>true) 930 } 931 932 Tk.update 933 934 $stdin = tio 935 $stdout = tio 936 $stderr = tio 937 938 STDOUT.print("\n========= TkTextIO#gets for inital text ========\n\n") 939 940 while(s = gets) 941 STDOUT.print(s) 942 end 943 944 STDOUT.print("\n============ put strings to TkTextIO ===========\n\n") 945 946 puts "On this sample, a text widget works as if it is a I/O stream." 947 puts "Please see the code." 948 puts 949 printf("printf message: %d %X\n", 123456, 255) 950 puts 951 printf("(output by 'p' method) This TkTextIO object is ...\n") 952 p tio 953 print(" [ Current wrap mode of this object is 'char'. ]\n") 954 puts 955 warn("This is a warning message generated by 'warn' method.") 956 puts 957 puts "current show_mode is #{tio.show_mode}." 958 if tio.show_mode == :pos 959 puts "So, you can see the current file position on this text widget." 960 else 961 puts "So, you can see the position '#{tio.show_mode}' on this text widget." 962 end 963 print("Please scroll up this text widget to see the head of lines.\n") 964 print("---------------------------------------------------------\n") 965 966 STDOUT.print("\n=============== TkTextIO#readlines =============\n\n") 967 968 tio.seek(0) 969 lines = readlines 970 STDOUT.puts(lines.inspect) 971 972 STDOUT.print("\n================== TkTextIO#each ===============\n\n") 973 974 tio.rewind 975 tio.each{|line| STDOUT.printf("%2d: %s\n", tio.lineno, line.chomp)} 976 977 STDOUT.print("\n================================================\n\n") 978 979 STDOUT.print("\n========= reverse order (seek by lines) ========\n\n") 980 981 tio.seek(-1, IO::SEEK_END) 982 begin 983 begin 984 tio.seek(:linestart, IO::SEEK_CUR) 985 rescue 986 # maybe use old version of tk/textmark.rb 987 tio.seek('0 char linestart', IO::SEEK_CUR) 988 end 989 STDOUT.print(gets) 990 tio.seek('-1 char linestart -1 char', IO::SEEK_CUR) 991 end while(tio.pos > 0) 992 993 STDOUT.print("\n================================================\n\n") 994 995 tio.seek(0, IO::SEEK_END) 996 997 STDOUT.print("tio.sync == #{tio.sync}\n") 998# tio.sync = false 999# STDOUT.print("tio.sync == #{tio.sync}\n") 1000 1001 (0..10).each{|i| 1002 STDOUT.print("#{i}\n") 1003 s = '' 1004 (0..1000).each{ s << '*' } 1005 print(s) 1006 } 1007 print("\n") 1008 print("\n=========================================================\n\n") 1009 1010 s = '' 1011 timer = TkTimer.new(:idle, -1, proc{ 1012 #STDOUT.print("idle call\n") 1013 unless s.empty? 1014 print(s) 1015 s = '' 1016 end 1017 }).start 1018 (0..10).each{|i| 1019 STDOUT.print("#{i}\n") 1020 (0..1000).each{ s << '*' } 1021 } 1022# timer.stop 1023 until s.empty? 1024 sleep 0.1 1025 end 1026 timer.stop 1027 1028=begin 1029 tio.sync = false 1030 print("\n") 1031 #(0..10000).each{ putc('*') } 1032 (0..10).each{|i| 1033 STDOUT.print("#{i}\n") 1034 (0..1000).each{ putc('*') } 1035 } 1036 1037 (0..10).each{|i| 1038 STDOUT.print("#{i}\n") 1039 s = '' 1040 (0..1000).each{ s << '*' } 1041 print(s) 1042 } 1043=end 1044 1045 num = 0 1046# io = TkTextIO.new(:mode=>:console, :prompt=>'').pack 1047#=begin 1048 io = TkTextIO.new(:mode=>:console, 1049 :prompt_cmd=>proc{ 1050 s = "[#{num}]" 1051 num += 1 1052 s 1053 }, 1054 :prompt=>'-> ').pack 1055#=end 1056 Thread.new{loop{sleep 2; io.puts 'hoge'}} 1057 Thread.new{loop{p io.gets}} 1058 1059 ev_loop.join 1060end 1061