diff -ur v2/argmnt.py v3/argmnt.py
--- v2/argmnt.py	2018-10-13 12:32:24.000000000 +0900
+++ v3/argmnt.py	2018-10-16 05:37:32.000000000 +0900
@@ -22,6 +22,15 @@
 class Empty:
 	pass
 
+str_width = nkf.str_width
+
+def is_line(o):
+	return hasattr(o, 'typ') and o.typ == 'line'
+
+def get_p2_vh(p1, p2):
+	(dx, dy) = rect.p_sub(p2, p1)
+	return 'v' if dx == 0 else 'h'
+
 def get_tx_rect(o):
 	return (o.x, o.y, o.w, o.h)
 
@@ -52,6 +61,21 @@
 	r = (x-1, y-1, w+2, h+2)
 	return r
 
+def get_line_rects(o):
+	rects = []
+	line_n = len(o.lst) - 1
+	for i in range( line_n ):
+		((x1,y1),(x2,y2)) = (p1, p2) = o.lst[i:i+2]
+		add = 1 if i == line_n - 1 else 0
+		if get_p2_vh(p1, p2) == 'h':
+			(x1,x2) = sorted( (x1,x2) )
+			r = (x1, y1, x2 - x1 + add, 1)
+		else:
+			(y1,y2) = sorted( (y1,y2) )
+			r = (x1, y1, 1, y2 - y1 + add)
+		rects += [r]
+	return rects
+
 def p_on_frm(p, o):
 	rects = rect.frame_rects( get_frm_rect(o) )
 	for r in rects:
@@ -60,7 +84,7 @@
 	return False
 
 def align_str(s, w, a):
-	m = w - nkf.str_width(s)
+	m = w - str_width(s)
 	if a == 'l':
 		s += ' ' * m
 	elif a == 'r':
@@ -88,10 +112,33 @@
 	if do_frm:
 		draw_frm_buf_atts(o, buf, frm_atts)
 
+def draw_line_buf_atts(o, buf, atts):
+	line_n = len(o.lst) - 1
+	for i in range( line_n ):
+		((x1,y1),(x2,y2)) = (p1, p2) = o.lst[i:i+2]
+		is_top = (i == 0)
+		is_tail = (i == line_n - 1)
+		if get_p2_vh(p1, p2) == 'h':
+			(x1,x2) = sorted( (x1,x2) )
+			buf.set( x1, y1, '-' * (x2-x1+1), atts )
+		else:
+			(y1,y2) = sorted( (y1,y2) )
+			for i in range(y2-y1+1):
+				buf.set(x1, y1+i, '|', atts)
+	for (x, y) in o.lst[1:-1]:
+		buf.set(x, y, '+', atts)
+
 def new(lst, nkf_opt):
 	e = Empty()
 
-	rects = list( map( get_frm_rect, lst ) )
+	rects = []
+	for o in lst:
+		if is_line(o):
+			rs = get_line_rects(o)
+		else:
+			rs = [ get_frm_rect(o) ]
+		rects += rs
+		
 	r = rect.rects_contain_rect(rects)
 	view = txview.new(r, nkf_opt)
 
@@ -109,11 +156,21 @@
 		return get_frm_rect(o) if frm_visible(o) else get_tx_rect(o)
 
 	def get_o_rects(o):
-		rects = [ get_tx_rect(o) ]
-		if frm_visible(o):
-			rects += rect.frame_rects( get_frm_rect(o) )
+		rects = []
+		if is_line(o):
+			rects += get_line_rects(o)
+		else:
+			rects += [ get_tx_rect(o) ]
+			if frm_visible(o):
+				rects += rect.frame_rects( get_frm_rect(o) )
 		return rects
 
+	def is_cross(o, r):
+		rects = get_o_rects(o) if is_line(o) else [ get_o_rect(o) ]
+		for r_ in rects:
+			if rect.is_cross(r_, r):
+				return True
+		return False
 	def get_atts(o, buf):
 		if o == e.sel:
 			if e.mode in ('sel', 'move', 'frm_align'):
@@ -132,63 +189,67 @@
 
 	def draw_o_buf(o, buf):
 		atts = get_atts(o, buf)
-		do_frm = frm_visible(o)
-		frm_atts = get_frm_atts(o, buf)
-		draw_o_buf_atts(o, buf, atts, do_frm, frm_atts)
+		if is_line(o):
+			draw_line_buf_atts(o, buf, atts)
+		else:
+			do_frm = frm_visible(o)
+			frm_atts = get_frm_atts(o, buf)
+			draw_o_buf_atts(o, buf, atts, do_frm, frm_atts)
 
-	def draw_r(r):
+	def draw_r(r, out=None):
+		r = view.scroll(r)
 		buf = txbuf.new(r)
 
 		for o in lst:
-			ro = get_o_rect(o)
-			if rect.is_area( rect.op_and(ro, r) ):
+			if is_cross(o, r):
 				draw_o_buf(o, buf)
-
 		# cursor
-		if e.mode == 'normal':
+		if e.mode in ('normal', 'sel_4', 'line'):
 			if rect.in_rect(e.cur, r):
 				(x, y) = e.cur
-				buf.set(x, y, '+')
+				atts = [ buf.rev ] if e.mode in ('sel_4', 'line') else []
+				buf.set(x, y, '+', atts)
 		buf.adjust()
 
 		(rx, ry, rw, rh) = r
+		ss = []
 		for i in range(rh):
 			y = ry + i
 			(x, s) = buf.get_line(y)
-			view.out(x, y, s)
-
-	def draw_r2(bak, r):
-		sc = view.scroll(r)
-
-		vr = view.get_rect()
-		rects = [ vr ]
-
-		if not sc:
-			bak = rect.op_and(bak, vr)
-			is_bak = rect.is_area(bak)
-
-			r = rect.op_and(r, vr)
-			is_r = rect.is_area(r)
-
-			if is_bak and is_r:
-				rects = rect.gather(bak, r)
-			elif is_bak:
-				rects = [ bak ]
-			elif is_r:
-				rects = [ r ]
-			else:
-				return
-		for r in rects:
-			draw_r(r)
+			ss.append( view.get_out_str(x, y, s) )
+		s = ''.join(ss)
+		if out:
+			out(s)
+		return s
 
 	def draw_o(o):
-		draw_r( get_o_rect(o) )
+		draw_r( get_o_rect(o), term.out )
+
+	def new_draw_rect(bak):
+		def f(r):
+			r = rect.rects_contain_rect( [ bak, r ] )
+			draw_r( r, term.out )
+		return f
+
+	def new_draw_rects(bak):
+		def f(rects):
+			for (b, r) in zip(bak, rects):
+				r = rect.rects_contain_rect( [ b, r ] )
+				draw_r( r, term.out )
+		return f
+
+	def new_draw(o):
+		if is_line(o):
+			f = new_draw_rects( get_o_rects(o) )
+			return lambda : f( get_o_rects(o) )
+		else:
+			f = new_draw_rect( get_o_rect(o) )
+			return lambda : f( get_o_rect(o) )
 
 	def move_cur(dx, dy):
-		bak = rect.p_wh_to( e.cur, (1, 1) )
+		f = new_draw_rect( rect.p_wh_to( e.cur, (1, 1) ) )
 		e.cur = rect.p_add( e.cur, (dx, dy) )
-		r = rect.p_wh_to( e.cur, (1, 1) )
-		draw_r2(bak, r)
+		f( rect.p_wh_to( e.cur, (1, 1) ) )
 
 	def get_o_under_cursor():
 		for o in reversed(lst):
@@ -200,14 +261,14 @@
 		if mode == 'sel':
 			e.sel = get_o_under_cursor()
 		o = e.sel
-		bak = get_o_rect(o) if o else None
-
+		f = new_draw(o) if o else None
 		e.mode = mode
 		if mode == 'normal':
 			e.sel = None
-
-		if o and bak:
-			draw_r2( bak, get_o_rect(o) )
+		if f:
+			f()
+		# cursor
+		draw_r( rect.p_wh_to( e.cur, (1, 1) ), term.out )
 
 	def normal(k):
 		d = key.allow(k)
@@ -229,20 +290,14 @@
 
 	def resize(k):
 		o = e.sel
-		bak = get_o_rect(o)
+		f = new_draw(o)
 		(bx, by) = o.frm.bdr
-		d = key.allow(k)
-		if d == 'u' and by > 0:
-			by -= 1
-		elif d == 'd':
-			by += 1
-		elif d == 'l' and bx > 0:
-			bx -= 1
-		elif d == 'r':
-			bx += 1
-		if (bx, by) != o.frm.bdr:
+		(dx, dy) = key.dxy( k, [0, 0] )
+		(bx, by) = ( max( bx+dx, 0 ), max( by+dy, 0 ) )
+		if [bx, by] != o.frm.bdr:
 			o.frm.bdr = [bx, by] # list
-			draw_r2( bak, get_o_rect(o) )
+			f()
+			#move_cur(dx, dy) # ...
 
 	def sel_align(k):
 		o = e.sel
@@ -253,9 +308,14 @@
 	def move(k):
 		o = e.sel
 		(dx, dy) = key.dxy( k, [0, 0] )
-		bak = get_o_rect(o)
-		(o.x, o.y) = (o.x + dx, o.y + dy)
-		draw_r2( bak, get_o_rect(o) )
+		f = new_draw(o)
+		if is_line(o):
+			for p in o.lst:
+				p[0] += dx
+				p[1] += dy
+		else:
+			(o.x, o.y) = (o.x + dx, o.y + dy)
+		f()
 		move_cur(dx, dy) # hidden
 
 	def sel_frm(k):
@@ -266,16 +326,40 @@
 
 	def frm_align(k):
 		o = e.sel
-		bak = get_o_rect(o)
+		f = new_draw(o)
 		d = key.allow(k)
 		if d in 'ud':
 			lst = ['u', 'c', 'd']
 			o.frm.va = lst[ ( lst.index(o.frm.va) + 1 ) % len(lst) ]
-			draw_r2( bak, get_o_rect(o) )
+			f()
 		elif d in 'lr':
 			lst = ['l', 'c', 'r']
 			o.frm.ha = lst[ ( lst.index(o.frm.ha) + 1 ) % len(lst) ]
-			draw_r2( bak, get_o_rect(o) )
+			f()
+
+	def line(k):
+		(dx, dy) = dxy = key.dxy( k, [0, 0] )
+		if dx == 0 and dy == 0:
+			return
+		if e.sel:
+			o = e.sel
+		else:
+			o = Empty()
+			o.typ = 'line'
+			o.lst = [ list(e.cur) ]
+			e.sel = o
+			lst.append(o)
+
+		vh = 'v' if dx == 0 else 'h'
+		p = list( rect.p_add(e.cur, dxy) )
+		if len(o.lst) >= 2 and get_p2_vh( *o.lst[-2:] ) == vh:
+			o.lst[-1] = p
+		else:
+			o.lst += [p]
+		if len(o.lst) >= 2 and o.lst[-1] == o.lst[-2]:
+			o.lst = o.lst[:-1]
+
+		move_cur(dx, dy)
 
 	def has_bdr(k):
 		(bx, by) = e.sel.frm.bdr
@@ -285,7 +369,7 @@
 		'normal': {
 			'ok': {
 				'f': ( lambda k: get_o_under_cursor() != None ),
-				True: 'sel' },
+				True: 'sel', False: 'sel_4' },
 			'allow': normal },
 		'sel': { 'ok': 'sel_2', 'allow': [ 'move', move ] },
 		'move': { 'ok': 'normal', 'allow': move },
@@ -306,6 +390,8 @@
 			'allow': [ 'resize', resize ] },
 		'resize': { 'ok': 'normal', 'allow': resize },
 		'frm_align': { 'ok': 'normal', 'allow': frm_align },
+		'sel_4': { 'ok': 'normal', 'allow': [ 'line', line ] },
+		'line': { 'ok': 'normal', 'allow': line },
 	}
 
 	def call(o, k):
@@ -343,7 +429,7 @@
 	e.mode = 'normal'
 	e.sel = None
 
-	draw_r(r)
+	draw_r( r, term.out )
 
 	return e
 
@@ -359,42 +445,49 @@
 	if type(o) == list:
 		o = { 's': o }
 	# dict
-	if 'xy' not in o:
-		o['xy'] = [ 1, y+1 ]
-		y += len( o.get('s') ) + 1
-	if 'a' not in o:
-		o['a'] = 'l'
-	if 'frm' not in o:
-		o['frm'] = { 'kind': '', 'va': 'c', 'ha': 'c', 'bdr': [ 0, 0 ] }
+	if 'typ' not in o:
+		if 'xy' not in o:
+			o['xy'] = [ 1, y+1 ]
+			y += len( o.get('s') ) + 1
+		if 'a' not in o:
+			o['a'] = 'l'
+		if 'frm' not in o:
+			o['frm'] = { 'kind': '', 'va': 'c', 'ha': 'c', 'bdr': [ 0, 0 ] }
 
 	e = to_empty(o)
-	if type(e.s) != list:
-		e.s = [ e.s ]
-	(e.x, e.y) = e.xy
-	e.w = max( map( nkf.str_width, e.s ) )
-	e.h = len(e.s)
-	e.frm = to_empty(e.frm)
+	if not hasattr(e, 'typ'):
+		if type(e.s) != list:
+			e.s = [ e.s ]
+		(e.x, e.y) = e.xy
+		e.w = max( map( str_width, e.s ) )
+		e.h = len(e.s)
+		e.frm = to_empty(e.frm)
 
 	return (e, y)
 
 def cvt_data_save(e):
-	if len(e.s) == 1:
-		e.s = e.s[0]
 	d = {}
-	d['s'] = e.s
-	d['xy'] = [ e.x, e.y ]
-	d['a'] = e.a
-
-	frm = e.frm
-	f = {}
-	f['kind'] = frm.kind
-	f['va'] = frm.va
-	f['ha'] = frm.ha
-	f['bdr'] = frm.bdr
-
-	d['frm'] = f
+	if hasattr(e, 'typ'):
+		d['typ'] = e.typ
+	if is_line(e):
+		d['lst'] = e.lst
+	else:
+		if len(e.s) == 1:
+			e.s = e.s[0]
+		d['s'] = e.s
+		d['xy'] = [ e.x, e.y ]
+		d['a'] = e.a
+	
+		frm = e.frm
+		f = {}
+		f['kind'] = frm.kind
+		f['va'] = frm.va
+		f['ha'] = frm.ha
+		f['bdr'] = frm.bdr
+	
+		d['frm'] = f
 	return d
-
+	
 def txt_out(lst, nkf_opt):
 	rects = list( map( get_frm_rect, lst ) )
 	r = rect.rects_contain_rect(rects)
diff -ur v2/rect.py v3/rect.py
--- v2/rect.py	2018-10-13 12:32:24.000000000 +0900
+++ v3/rect.py	2018-10-16 05:36:26.000000000 +0900
@@ -18,6 +18,14 @@
 	( (x, y), (w, h) ) = (p, wh)
 	return (x, y, w, h)
 
+def get_p(r):
+	(x, y, w, h) = r
+	return (x, y)
+
+def get_wh(r):
+	(x, y, w, h) = r
+	return (w, h)
+
 def to_p2(r):
 	(x, y, w, h) = r
 	p = (x, y)
@@ -40,6 +48,9 @@
 	(x, y, w, h) = r
 	return w > 0 and h > 0
 
+def is_cross(ra, rb):
+	return is_area( op_and(ra, rb) )
+
 def in_rect(p, r):
 	(px, py) = p
 	( (x, y), (x2, y2) ) = to_p2(r)
@@ -81,7 +92,7 @@
 
 def gather(ra, rb): # return rects
 	(x, y, w, h) = r = op_and(ra, rb)
-	if w < 0 or h < h: # ! don't use is_area()
+	if w < 0 or h < 0: # ! don't use is_area()
 		return [ ra, rb ]
 	if r == ra:
 		return [ rb ]
@@ -116,7 +127,21 @@
 		if not rects or ( rects[-1][0] != x or rects[-1][0] + rects[-1][2] != x2 ):
 			rects += [ (x, y_, x2-x, 1) ]
 		else:
-			(x, y, w, h) = rects[-1]
-			rects[-1] = (x, y, w, h+1)
+			(xx, yy, ww, hh) = rects[-1]
+			rects[-1] = (xx, yy, ww, hh+1)
 	return rects
+
+def rect_sub(a, b): # a >= b
+	((ax,ay),(ax2,ay2)) = to_p2(a)
+	((bx,by),(bx2,by2)) = to_p2(b)
+	ret = []
+	if ay != by:
+		ret += [ p2_to((ax,ay),(ax2,by)) ]
+	if ay2 != by2:
+		ret += [ p2_to((ax,by2),(ax2,ay2)) ]
+	if ax2 != bx2:
+		ret += [ p2_to((bx2,by),(ax2,by2)) ]
+	if ax != bx:
+		ret += [ p2_to((ax,by),(bx,by2)) ]
+	return ret
 # EOF
diff -ur v2/term.py v3/term.py
--- v2/term.py	2018-10-10 22:08:59.000000000 +0900
+++ v3/term.py	2018-10-16 05:36:26.000000000 +0900
@@ -41,10 +41,15 @@
 esc = lambda s: chr(0x1b) + '[' + s
 esc_ex = lambda d, v: esc( '?{}{}'.format(d, 'h' if v else 'l' ) )
 
-loc = lambda x, y: out( esc( '{};{}H'.format(y+1, x+1) ) )
-rev = lambda : out( esc('7m') )
-uline = lambda : out( esc('4m') )
-reset = lambda : out( esc('0m') )
+s_loc = lambda x, y: esc( '{};{}H'.format(y+1, x+1) )
+s_rev = esc('7m')
+s_uline = esc('4m')
+s_reset = esc('0m')
+
+loc = lambda x, y: out( s_loc(x, y) )
+rev = lambda : out( s_rev )
+uline = lambda : out( s_uline )
+reset = lambda : out( s_reset )
 cls = lambda : out( esc('2J') )
 cursor_show = lambda v: out( esc_ex(25, v) )
 
diff -ur v2/txbuf.py v3/txbuf.py
--- v2/txbuf.py	2018-10-12 22:19:16.000000000 +0900
+++ v3/txbuf.py	2018-10-16 05:36:26.000000000 +0900
@@ -25,9 +25,9 @@
 	(rx, ry, rw, rh) = r
 	arr_r = (rx-1, ry, rw+2, rh)
 
-	e.rev = term.esc('7m')
-	e.uline = term.esc('4m')
-	e.reset = term.esc('0m')
+	e.rev = term.s_rev
+	e.uline = term.s_uline
+	e.reset = term.s_reset
 
 	e.arr = list( map( lambda i: ' ' * (rw+2), range(rh) ) )
 	e.att = list( map( lambda i: [[]] * (rw+2), range(rh) ) )
diff -ur v2/txview.py v3/txview.py
--- v2/txview.py	2018-10-12 22:19:16.000000000 +0900
+++ v3/txview.py	2018-10-16 05:36:26.000000000 +0900
@@ -52,19 +52,25 @@
 	to_view = lambda x, y: (x - e.sx, y - e.sy)
 	view_to = lambda x, y: (x + e.sx, y + e.sy)
 
-	def out(x, y, s):
+	def get_out_str(x, y, s):
 		s = nkf.enc(s)
 		if nkf_opt != '-u':
 			s = nkf.cvt(s, nkf_opt)
 		(x, y) = to_view(x, y)
-		term.loc( x + e.x, y + e.y )
-		term.out(s)
-	e.out = out
+		return term.s_loc( x + e.x, y + e.y ) + s
+	e.get_out_str = get_out_str
+
+	e.out = lambda x, y, s: term.out( get_out_str(x, y, s) )
+
+	e.get_rect = lambda : rect.cvt_fxy( view_to, (0, 0, e.w, e.h) )
 
 	def scroll(r):
 		vr = rect.cvt_fxy( to_view, r )
 		if rect.r_in_rect( vr, (0, 0, e.w, e.h) ):
-			return False
+			return r
+
+		bak_p = rect.p_add( to_view( *rect.get_p(r) ), (e.x, e.y) )
+		bak_sc = (e.sx, e.sy)
 
 		(x, y, w, h) = r
 		( (vx, vy), (vx2, vy2) ) = rect.to_p2(vr)
@@ -72,11 +78,17 @@
 		(e.x, e.w, e.sx) = sc_sub(vx, vx2, t_w, e.x, e.w, e.sx)
 		(e.y, e.h, e.sy) = sc_sub(vy, vy2, t_h, e.y, e.h, e.sy)
 
-		return True
+		all = e.get_rect()
+		p = rect.p_add( to_view( *rect.get_p(r) ), (e.x, e.y) )
+		if p == bak_p:
+			if bak_sc[1] != e.sy:
+				(ax, ay, aw, ah) = all
+				(x, y, w, h) = r
+				return (ax, y, aw, h)
+			return r
+		return all
 	e.scroll = scroll
 
-	e.get_rect = lambda : rect.cvt_fxy( view_to, (0, 0, e.w, e.h) )
-
 	e.fini = lambda : term.loc( 0, e.y + e.h )
 
 	return e
