# Copyright (C) 1997,1998 Kikutani Makoto (kikutani@debian.or.jp) require "slanglib" include Slanglib module Slang SLANG_VERSION = 51 SL_MOUSE_B0 = 0x1101 SL_MOUSE_B1 = 0x1102 SL_MOUSE_B2 = 0x1103 PAGER_QUIT = 0x1111 PAGER_KEY_UP = 0x1112 PAGER_KEY_DOWN = 0x1113 PAGER_NPAGE = 0x1114 PAGER_PPAGE = 0x1115 PAGER_BOB = 0x1116 PAGER_EOB = 0x1117 PAGER_REDRAW = 0x1118 PAGER_KEY_RIGHT = 0x1119 PAGER_KEY_LEFT = 0x111a PAGER_SEARCH_FWD = 0x111b PAGER_SEARCH_BWD = 0x111c PAGER_SEARCH_NEXT = 0x111d SL_MOUSE_B3 = 0x111e SL_MOUSE_B4 = 0x111f BRANCH = 3 LAST_THREAD = 2 SAME_LEVEL_THREAD = 1 BLANK = 0 class SlangSearch < StandardError; end $slang_mouse_mode = FALSE $after_mini_message = 0 # same as sl_reset # is this necessary ? def slang_reset slsmg_gotorc(sltt_screen_rows - 1, 0) slsmg_refresh sltt_set_mouse_mode(0, 1) if $slang_mouse_mode sl_reset_tty slsmg_reset_smg end def slang_suspend sltt_set_mouse_mode(0, 1) if $slang_mouse_mode #reset_tty #slsmg_reset_smg slsmg_suspend_smg end def slang_resume # init_tty(-1, 0, 1) # slsmg_init_smg slsmg_resume_smg sltt_set_mouse_mode(1, 1) if $slang_mouse_mode end def init_terminal(tty, smg) slsig_block_signals sltt_get_terminfo $old_row = sltt_screen_rows $old_col = sltt_screen_cols # SLkp_init assumes that SLtt_get_terminfo has been called. if tty && -1 == slkp_init slsig_unblock_signals return FALSE end sl_init_signals slang_init_tty(-1, 0, 1) if tty sltty_set_suspend_state(1) if tty slsmg_init_smg if smg slsig_unblock_signals sl_init_colors if sl_color_terminal? sltt_set_mouse_mode(1, 1) if $slang_mouse_mode return TRUE end def mouse_on $slang_mouse_mode = TRUE sl_mouse_on end def mouse_off $slang_mouse_mode = FALSE sl_mouse_off end # the following minibuffer functions are retrieved from 'most' sources def mini_message(what, how) @mini_buf = what if how @beep_mini = TRUE else @beep_mini = FALSE end end def select_minibuffer slsmg_gotorc(sltt_screen_rows - 1, 0) slsmg_erase_eol # slsmg_refresh @last_message = nil @minibuffer_selected = TRUE end def exit_minibuffer # slsmg_gotorc(sltt_screen_rows - 1, 0) # slsmg_erase_eol @minibuffer_selected = FALSE end def put_message_1(what) select_minibuffer slsmg_write_string(what) # exit_minibuffer @last_message = what end def flush_message(msg) slsmg_gotorc(sltt_screen_rows - 1, 0) slsmg_write_string(msg) slsmg_erase_eol slsmg_refresh end def put_message sltt_beep if @beep_mini put_message_1(@mini_buf) if @beep_mini slsmg_refresh sleep 0.5 end @beep_mini = TRUE @mini_buf = nil $after_mini_message = 1 end def sl_put_mini_message(msg, how) mini_message(msg, how) select_minibuffer put_message # exit_minibuffer end def clear_minibuffer @mini_buf = nil @Beep_Mini = FALSE put_message end def check_minibuffer if @mini_buf put_message else if @last_message != global_msg put_message_1(global_msg) end end end def completion(str) if $completion_hook if str != $completion_str $completion_str = str begin $completion_array = $completion_hook.call(str) rescue $completion_array = [] end end end return str if $completion_array == [] $completion_index = 0 if $completion_index >= $completion_array.size s = $completion_array[$completion_index] $completion_index += 1 s end def sl_read_mini(prompt, ini=nil, noecho=nil) select_minibuffer $completion_str = nil $completion_array = [] len, str = sl_read_line(prompt, ini, noecho) exit_minibuffer return len, str end def sl_read_mini_with_compl(prompt, ini=nil, noecho=nil) select_minibuffer $completion_str = nil $completion_index = 0 len, str = sl_read_line(prompt, ini, noecho) exit_minibuffer return len, str end def sl_read_mini_with_file_compl(prompt, ini=nil, noecho=nil) select_minibuffer $completion_str = nil $completion_index = 0 $completion_hook = Proc.new{|s| Dir.glob(File.expand_path(s + "*"))} len, str = sl_read_line(prompt, ini, noecho) exit_minibuffer return len, str end def sl_read_mini_key(msg, keys) select_minibuffer slsmg_write_string(msg) slsmg_refresh while TRUE k = slang_getkey break if keys.include?(k) if k == 0x07 # CTL-G k = -1 break end end exit_minibuffer k end class Line_element def initialize(s='') @id = 0 @data = s @attribute = nil @continued_line = nil @continued_top = nil @long_line = nil @special_write = nil end attr("id", TRUE) attr("data", TRUE) attr("attribute", TRUE) attr("special_write", TRUE) attr("continued_line", TRUE) attr("continued_top", TRUE) attr("long_line", TRUE) end class Tree_element def initialize(p = nil) @id = 0 @parent = p @node = nil @uncollapsed = FALSE @depth = 0 @hier_list = [] @dval = {} @last_element = FALSE @num_of_children = 0 @fake_thread = nil end attr("id", TRUE) attr("parent", TRUE) attr("node", TRUE) attr("uncollapsed", TRUE) attr("depth", TRUE) attr("hier_list", TRUE) attr("last_element", TRUE) attr("num_of_children", TRUE) attr("fake_thread", TRUE) def set_val(key, val) @dval[key] = val end def get_val(key) @dval[key] end end class Pager def initialize(row_min, row_max, col_min, col_max, lelement=nil, offset=0) @rmin = row_min @rmax = row_max @cmin = col_min @cmax = col_max @cursor_move = nil @cursor_min = 0 # not used @cursor_max = 0 # not used if lelement @lelement = lelement else @lelement = Line_element end @with_mini_buffer = nil @revcolor = SL_BG_BLUE + SL_YELLOW # BG=BLUE, FG=YELLOW @current_row = 0 @col_start = 0 @scrw = Scroll_window.new @key_actions = {} @bar = 0 @tilde = 1 @screen_shift_rate = 3/4.0 @upd_obj = self init_key_actions special_key_actions @hlines = {} @lines = [] @lines_num = 0 @inhibit_horiz_scroll = nil @search_dir = 1 @search_str = nil @default_cursor_pos = [] @offset = offset @highlight_rex = nil build_lines end attr("scrw") attr("rmin", TRUE) attr("rmax", TRUE) attr("cmin", TRUE) attr("cmax", TRUE) attr("current_row", TRUE) attr("revcolor", TRUE) attr("screen_shift_rate", TRUE) attr("bar", TRUE) attr("tilde", TRUE) attr("inhibit_horiz_scroll", TRUE) attr("lines") attr("default_cursor_pos", TRUE) attr("offset") attr :highlight_rex attr :highlight_color, true def get_current_line return line_num() + @current_row - @offset - 1 end def set_highlight_rex(rex) @highlight_rex = rex end def set_cursor(cursor_move, cursor_min, cursor_max) @cursor_move = cursor_move @cursor_min = cursor_min # not used @cursor_max = cursor_max if @cursor_move @current_row = @cursor_min end end def set_mini_buffer(v) @with_mini_buffer = v if v == TRUE @cursor_max = @rmax - 2 else @cursor_max = @rmax - 1 end end def set_bar(n) @bar = n @scrw.set_bar(@bar) end def set_tilde(n) @tilde = n @scrw.set_tilde(@tilde) end def init_lines @scrw.init_lines(@lines_num) end private :init_lines def build_lines # in this method # @hlines[id] will be filled with @lelement # @lines_num should be updated # you shoud call init_lines at the end of this method end def add_line(str, a=nil) if str.kind_of?(String) l = @lelement.new(str) else l = str end id = @scrw.add_line(l) return nil if 0 == id l.id = id l.attribute = a if a @lines[@lines_num] = l @lines_num += 1 return l end private :add_line def replace_line(old, str, a=nil) if str.kind_of?(String) l = @lelement.new(str) else l = str end newid = @scrw.replace_line(old.id, l, @current_row) return nil unless newid l.id = newid l.attribute = a if a @hlines[newid] = l # to avoid being erased by GC @hlines.delete(old.id) newid end private :replace_line def draw_bottom end private :draw_bottom def normal_color if sl_color_terminal? slsmg_set_color(@scrw.get_normal_color) else slsmg_normal_video end end private :normal_color # usually used for bottom line def reverse_color if sl_color_terminal? slsmg_set_color(@revcolor) else slsmg_reverse_video end end private :reverse_color def cursor_color if sl_color_terminal? slsmg_set_color(@scrw.get_cursor_line_color) else slsmg_reverse_video end end private :cursor_color def update_display slsig_block_signals begin normal_color if @cursor_move res = update_region(@rmin, @rmax, @cmin, @cmax, @col_start, @current_row) unless res if @with_mini_buffer sl_put_mini_message('something wrong in update_display', TRUE) end return FALSE end draw_bottom # normal_color if @with_mini_buffer unless @minibuffer_selected slsmg_gotorc(sltt_screen_rows-1, 0) slsmg_erase_eol end end slsmg_gotorc(@current_row, @cmin) else res = update_region(@rmin, @rmax, @cmin, @cmax, @col_start, -1) unless res if @with_mini_buffer sl_put_mini_message('something wrong in update_display', TRUE) end return FALSE end draw_bottom if @with_mini_buffer unless @minibuffer_selected slsmg_gotorc(sltt_screen_rows-1, 0) slsmg_erase_eol end end if @default_cursor_pos != [] slsmg_gotorc(@default_cursor_pos[0], @default_cursor_pos[1]) else slsmg_gotorc(@rmin, @cmin) end end slsmg_refresh return TRUE ensure slsig_unblock_signals end end def goto_bob while TRUE break if @scrw.scroll_pageup == -1 end if @cursor_move @current_row = @rmin end end def goto_eob while TRUE # break if @scrw.scroll_pagedown == -1 break unless next_page end end def scroll_prev_n(n) @scrw.scroll_prev_n(n) end def back_scr if !@cursor_move scroll_prev_n(1) else if @current_row > @rmin @current_row -= 1 else scroll_prev_n(1) end end end def line_num @scrw.line_num end def num_lines @scrw.num_lines end def scroll_next_n(n) @scrw.scroll_next_n(n) end def forw_scr if !@cursor_move scroll_next_n(1) else if @scrw.line_num+@current_row < @rmin + @scrw.num_lines if @current_row < @cursor_max @current_row += 1 else scroll_next_n(1) end end end end def next_page if @scrw.line_num + (@rmax-@rmin) - 1 < @scrw.num_lines @scrw.scroll_pagedown @scrw.top_window_line_is_current_line if @scrw.line_num+@current_row > @scrw.num_lines if @cursor_move @current_row = @scrw.num_lines - @scrw.line_num + @rmin end end return TRUE else # already last page if @cursor_move @current_row = @scrw.num_lines - @scrw.line_num + @rmin end return FALSE end end def prev_page if @scrw.scroll_pageup < 0 # already top page @current_row = @rmin if @cursor_move end end def key_actions_add(keys, action = Proc.new) keys.each{|k| @key_actions[k] = action} end private :key_actions_add def post_key_hook(k) exit_minibuffer if $after_mini_message == 0 $after_mini_message -= 1 if $after_mini_message > 0 end private :post_key_hook def undefined_key_hook(k) end private :undefined_key_hook def key_actions_call(k) if p = @key_actions[k] p.call post_key_hook(k) else undefined_key_hook(k) sltt_beep end @do_break end private :key_actions_call # method for mouse click def mouse_b0(c, r) end private :mouse_b0 def mouse_b1_status(c, r) end private :mouse_b1_status def mouse_b1(c, r) end private :mouse_b1 def mouse_b2(c, r) end private :mouse_b2 def do_search_next(research) cur_line = @scrw.line_num - 1 @scrw.find_set_nth_line(cur_line) n = 0 while TRUE # assume data is string! line = @scrw.find_current_line.data if @search_str =~ line break if n != 0 break unless research end if @search_dir > 0 res = @scrw.find_next_line else res = @scrw.find_prev_line end unless res raise SlangSearch end n += @search_dir end goto_bob @scrw.scroll_next_n(cur_line + n) end def do_search_fwd len, str = sl_read_mini("Forward Search(regexp): ") begin @search_str = Regexp.new(str) rescue RegxpError sl_put_mini_message("invalid regexp", TRUE) return end if len > 0 @search_dir = 1 do_search_next(FALSE) end end def do_search_bwd len, str = sl_read_mini("Backward Search(regexp): ") begin @search_str = Regexp.new(str) rescue RegxpError sl_put_mini_message("invalid regexp", TRUE) return end if len > 0 @search_dir = -1 do_search_next(FALSE) end end # basic pager actions def init_key_actions key_actions_add([PAGER_QUIT]) do @do_break = TRUE # may be overridden by subclass end key_actions_add([SL_KEY_RIGHT, PAGER_KEY_RIGHT]) do unless @inhibit_horiz_scroll @col_start += (@cmax - @cmin) * @screen_shift_rate # tmp, newp = slsmg_set_screen_start (0, @screen_start) end end key_actions_add([SL_KEY_LEFT, PAGER_KEY_LEFT]) do unless @inhibit_horiz_scroll @col_start -= (@cmax - @cmin) * @screen_shift_rate @col_start = 0 if @col_start < 0 # tmp, newp = slsmg_set_screen_start (0, @screen_start) end end key_actions_add([SL_KEY_UP, PAGER_KEY_UP]) do back_scr end key_actions_add([SL_KEY_DOWN, PAGER_KEY_DOWN]) do forw_scr end key_actions_add([SL_KEY_NPAGE, PAGER_NPAGE]) do next_page end key_actions_add([SL_KEY_PPAGE, PAGER_PPAGE]) do prev_page end key_actions_add([SL_KEY_HOME, PAGER_BOB]) do goto_bob end key_actions_add([SL_KEY_END, PAGER_EOB]) do goto_eob end key_actions_add([PAGER_REDRAW]) do slsmg_cls update_display end key_actions_add([PAGER_SEARCH_FWD]) do begin do_search_fwd rescue SlangSearch sl_put_mini_message("not found", TRUE) end end key_actions_add([PAGER_SEARCH_BWD]) do begin do_search_bwd rescue SlangSearch sl_put_mini_message("not found", TRUE) end end key_actions_add([PAGER_SEARCH_NEXT]) do begin do_search_next(TRUE) rescue SlangSearch sl_put_mini_message("not found", TRUE) end end # define the behavior of status line click key_actions_add([SL_MOUSE_B0]) do c, r = sl_get_mouse_rc if r == @rmax next_page elsif r < @rmax and r >= @rmin mouse_b0(c, r) end end key_actions_add([SL_MOUSE_B1]) do c, r = sl_get_mouse_rc if r == @rmax mouse_b1_status(c, r) elsif r < @rmax and r >= @rmin mouse_b1(c, r) end end key_actions_add([SL_MOUSE_B2]) do c, r = sl_get_mouse_rc if r == @rmax prev_page elsif r < @rmax and r >= @rmin mouse_b2(c, r) end end end private :init_key_actions def special_key_actions end private :special_key_actions def win_row_resize @rmax = sltt_screen_rows - ($old_row - @rmax) end def win_col_resize @cmax = sltt_screen_cols - ($old_col - @cmax) end def win_resize win_row_resize win_col_resize end def post_winch_hook end # if window size is modified by mouse drag def check_winch if sl_get_winch_flag == 1 sltt_get_screen_size if $old_row != sltt_screen_rows or $old_col != sltt_screen_cols slsmg_init_smg win_resize post_winch_hook() $old_row = sltt_screen_rows $old_col = sltt_screen_cols end sl_clr_winch_flag end end def split_long_line(str, len) first = TRUE lines = [] l = sl_expand_tab(str).dup while l.size > len str0 = l[0, len+1] # judge if last char is 2nd byte of KANJI # the following match is affected by $KCODE value if str0 =~ /.$/ and $&.size == 2 str0[-2, 2] = ' ' l = l[len-1..-1] else l = l[len..-1] end lines << str0.chop if first first = FALSE len -= 1 # 2nd and after strings are len -1 for CONTINUE mark end end lines << l if l lines end # wrap a long line into short lines def wrap @inhibit_horiz_scroll = TRUE len = @cmax - @cmin + 1 num = 0 return unless @lines[0].data.kind_of?(String) @lines.each {|l| str = l.data if sl_tabbed_len(str) > len al = split_long_line(str, len) if al.size >= 2 s0 = @lelement.new(al[0]) newid = @scrw.replace_line(l.id, s0, @current_row) if newid s0.id = newid s0.continued_top = TRUE s0.long_line = str s0.attribute = l.attribute s0.special_write = l.special_write @hlines[newid] = s0 # to avoid being erased by GC @hlines.delete(l.id) @lines[num] = s0 end now = s0 j = 1 al[1..-1].each{|s| sn = @lelement.new('+' + s) newid = @scrw.insert_line(now.id, sn) if newid sn.id = newid sn.continued_line = TRUE sn.continued_top = s0 sn.attribute = l.attribute sn.special_write = l.special_write @hlines[newid] = sn # to avoid being erased by GC @lines[num + j, 0] = [sn] @lines_num += 1 now = sn end j += 1 } end end num += 1 } end def unwrap @inhibit_horiz_scroll = FALSE num = 0 while l = @lines[num] unless l.continued_top num += 1 else s0 = @lelement.new(l.long_line) newid = @scrw.replace_line(l.id, s0, @current_row) if newid s0.id = newid s0.continued_top = FALSE s0.attribute = l.attribute s0.special_write = l.special_write @hlines[newid] = s0 # to avoid being erased by GC @hlines.delete(l.id) @lines[num] = s0 end num += 1 while (l = @lines[num]).continued_line @scrw.remove_line(l.id) @hlines.delete(l.id) @lines.delete_at(num) @lines_num -= 1 end end end end def main_loop @do_break = FALSE while TRUE check_winch update_display sleep(0.1) if (IO::select([ STDIN ])) key_actions_call(slkp_getkey) break if @do_break end end def main_loop_c @scrw.main_loop(self) end # called from slmodule.c def get_hlarray(line, hr=nil) rex = hr || @highlight_rex return nil unless rex pa = [] rex.each do |r| color = r[0] regexp = r[1] search_pos = 0 line.scan(regexp) do |sa| sa.each do |s| b = line.index(s, search_pos) len = s.length pa << [b, len, color] search_pos = b + len end end end pa.sort end # write_line is called from slmodule.c/scrW_update_region # this method will be usually overriden def write_line(l, row, hl_rex=nil, ncol=nil) line = l.data ncolor = ncol || @scrw.get_normal_color if l.special_write call l.special_write else sl_write_string_with_offset(self, line, @col_start, @cmax, hl_rex, ncolor) end end private :write_line def update_region(b, e, cb, ce, off, curline) return if e <= b || ce <= cb @scrw.update_region(self, b, e, cb, ce, off, curline) end # update_region end class Tree_Pager < Pager def initialize(row_min, row_max, col_min, col_max, lelement, offset=0) @aformat = [] @uncollapse_all = FALSE @dia = FALSE super(row_min, row_max, col_min, col_max, lelement, offset) set_cursor(TRUE, row_min, row_max-1) end def build_index # similar to build_lines, but not called from new() end def set_format(f) @format = f end def set_uncollapse_all @uncollapse_all = TRUE end def unset_uncollapse_all @uncollapse_all = FALSE end def set_dia @dia = TRUE end def unset_dia @dia = FALSE end def replace_line(old, l) newid = @scrw.replace_line(old.id, l, @current_row) return nil unless newid l.id = newid @hlines[newid] = l # to avoid being erased by GC @hlines.delete(old.id) l.node = old.node l.parent = old.parent l.depth = old.depth l.uncollapsed = old.uncollapsed l.hier_list = old.hier_list l.num_of_children = old.num_of_children l.last_element = old.last_element l.fake_thread = old.fake_thread newid end private :replace_line def insert_elements(l) # should be called from uncollapse end private :insert_elements def insert_line(id, l, num) newid = @scrw.insert_line(id, l) return nil unless newid l.id = newid @hlines[newid] = l # to avoid being erased by GC @lines[num, 0] = [l] # insert as the next @lines_num += 1 newid end private :insert_line def uncollapse(num) l = @lines[num] # insert_elements should be defined in subclass uncollapsed_list = insert_elements(num) l.num_of_children = uncollapsed_list.size l.uncollapsed = TRUE last = TRUE # reverse analysis for tree display uncollapsed_list.reverse_each { |r| if last hlist = [LAST_THREAD] r.last_element = TRUE last = FALSE elsif r == uncollapsed_list[0] hlist = [BRANCH] else hlist = [BRANCH] end upper_list = l.hier_list if upper_list != [] if r.parent and not r.parent.last_element upper_list[-1] = SAME_LEVEL_THREAD else upper_list[-1] = BLANK end end r.hier_list = upper_list + hlist } if l.last_element l.hier_list[-1] = LAST_THREAD elsif l.hier_list != [] l.hier_list[-1] = BRANCH end inc = 0 if @uncollapse_all n = num + 1 uncollapsed_list.each { |l| rl = @lines[n] if rl.node and rl.uncollapsed == FALSE res = uncollapse(n) n += res inc += res end n += 1 } end uncollapsed_list.size + inc end private :uncollapse def collapse(num) m = @lines[num] if m.num_of_children == 0 m.uncollapsed = FALSE return end cur_depth = m.depth num += 1 while TRUE l = @lines[num] break if l.nil? # exceed line list d = l.depth break if d <= cur_depth if l.node and l.uncollapsed collapse(num) end @scrw.remove_line(l.id) @hlines.delete(l.id) # to be erased by GC (but work ?) @lines.delete_at(num) @lines_num -= 1 end m.uncollapsed = FALSE end private :collapse def coll_uncoll num = @scrw.line_num + @current_row - 1 - @rmin if l = @lines[num] if l.node if l.uncollapsed collapse(num) else uncollapse(num) end end end end private :coll_uncoll # difficult ? yup! def write_tree(l, width, row) w = 0 if l.depth == 0 if l.node if l.uncollapsed slsmg_write_char(32) w += 1 else if @dia slsmg_set_color($tree_color) unless row == @current_row slsmg_set_char_set(1) slsmg_write_char(96) # dia slsmg_set_char_set(0) else slsmg_write_char(32) end w += 1 end else slsmg_write_char(32) w += 1 end else slsmg_write_char(32) w += 1 slsmg_set_color($tree_color) unless row == @current_row slsmg_set_char_set(1) for i in 0 .. l.depth-1 do if l.hier_list[i] == SAME_LEVEL_THREAD slsmg_write_char(120) # | slsmg_write_char(32) w += 2; break if width > 0 and w >= width elsif l.hier_list[i] == BRANCH slsmg_write_char(116) # |- if l.fake_thread and i == l.depth-1 slsmg_set_char_set(0) # because of stupid kterm slsmg_write_char(?*) # - slsmg_set_char_set(1) else slsmg_write_char(113) # - end w += 2; break if width > 0 and w >= width elsif l.hier_list[i] == LAST_THREAD slsmg_write_char(109) # L if l.fake_thread and i == l.depth-1 slsmg_set_char_set(0) # because of stupid kterm slsmg_write_char(?*) # - slsmg_set_char_set(1) else slsmg_write_char(113) # - end w += 2; break if width > 0 and w >= width elsif l.hier_list[i] == BLANK slsmg_write_char(32) slsmg_write_char(32) w += 2; break if width > 0 and w >= width end end unless l.uncollapsed if l.node if @dia slsmg_write_char(96) # dia else slsmg_write_char(113) # - end else slsmg_set_char_set(0) # because of stupid kterm slsmg_write_char(?>) end w += 1 end slsmg_set_char_set(0) end normal_color unless row == @current_row w # return how many chars are used for tree end private :write_tree # convert format string to array # to make write_line faster def set_aformat @format.split.each {|s| format_char = sprintf("%c", s[-1]) # last char @aformat << [format_char, s.to_i] } end private :set_aformat end end