git » msnlib » commit 34e8994

Implement tab completion.

author Alberto Bertogli
2005-04-09 18:44:12 UTC
committer Alberto Bertogli
2005-04-09 18:44:12 UTC
parent c75db00523331b0ba114853655445eda3f197480

Implement tab completion.
This patch implements tab completion not only for nicks/emails but also for
commands. It still doesn't handle command arguments (like "status o<TAB>"
doesn't complete "online"), but it's not that important anyway.

It was sent by Sebastian Santisi <s@ntisi.com.ar> (I made only small
modifications).

msn +200 -60

diff --git a/msn b/msn
index 1070f80..9e64de3 100644
--- a/msn
+++ b/msn
@@ -115,6 +115,13 @@ color_classes = {
 c = color_classes['default']()
 
 
+# command list for tab-completion purposes
+command_list = [ 'a', 'add', 'block', 'close', 'color', 'config', 'del', 'e',
+	'ee', 'eg', 'g', 'gadd', 'gdel', 'green', 'h', 'help', 'info',
+	'invite', 'lignore', 'luignore', 'm', 'nick', 'privacy', 'q', 'r',
+	'ren', 'status', 'unblock', 'w', 'wr', 'ww' ]
+
+
 #
 # different useful prints
 #
@@ -344,31 +351,70 @@ def email2nick(email):
 	else:
 		return None
 
-def matchemail(begin):
+def findemailnick(email, begin):
+	"""Check if the email/nick of the given user begins with the given
+	beginning. Returns 1 if it matches the nick, or 2 if it matches the
+	email."""
+	if m.users[email].nick.find(begin) == 0:
+		return 1
+	elif email.find(begin) == 0:
+		return 2
+	return 0
+
+# global variable for matchemail()
+start_from = 0
+def matchemail(begin, only_online = 0, start = 0):
 	""""Returns a matching email/nick for the given beginning; it avoids
-	nicks with spaces"""
-	l = len(begin)
-	# first try the ones with sbd
-	for email in m.users.keys():
-		if not m.users[email].sbd:
-			continue
-		nick = m.users[email].nick
-		if ' ' in nick:
-			nick = email
-		if len(nick) >= l and nick[0:l] == begin:
-			return nick
-	# then the nicks
-	for email in m.users.keys():
-		nick = m.users[email].nick
-		if ' ' in nick:
-			continue
-		if len(nick) >= l and nick[0:l] == begin:
-			return nick
-	# and finally the emails
-	for email in m.users.keys():
-		if len(email) >= l and email[0:l] == begin:
-			return email
-	return None
+	beginnings with spaces. If only_online is equal to 1 searchs only for
+	not offline users or users with an sbd.  If start=1 it iterates the
+	last match and do a cyclical search."""
+
+	global start_from
+
+	if ' ' in begin:
+		return None
+
+	emails = m.users.keys()
+
+	if start_from >= len(emails):
+		# the list has changed while iterating, reset
+		start_from = 0
+
+	if start:
+		pos = start_from + 1
+	else:
+		pos = 0
+
+	found = 0
+	while not found:
+		# we made a complete loop without matches
+		if start and pos == start_from:
+			break
+		elif pos == len(emails) - 1:
+			break
+
+		#msnlib.debug("l: %d %s\n" % (pos, emails[pos]))
+		current = emails[pos]
+		if only_online and m.users[current].status == 'FLN':
+				pos = (pos + 1) % len(emails)
+				continue
+		if findemailnick(emails[pos], begin):
+			found = 1
+			break
+		pos = (pos + 1) % len(emails)
+
+	start_from = pos + 1 % len(emails)
+
+	if found:
+		if findemailnick(emails[pos], begin) == 1:
+			# return the nick
+			return email2nick(emails[pos])
+		else:
+			return emails[pos]
+	else:
+		start_from = 0
+		return None
+
 
 def gname2gid(gname):
 	"Returns a group name according to the given group id"
@@ -506,6 +552,20 @@ screenwidth = winsize[1]
 # input buffer, where all the characters written by the user are stored in
 inbuf = ''
 
+# vars to control the tabs completions:
+# match of commands
+matchc_last = 0
+matchc_status = 0
+matchc_root = ''
+# match of last send/received
+matchl_status = 0
+# match of m command
+matchm_status = 0
+matchm_root = ''
+# match of others arguments
+matchp_status = 0
+matchp_root = ''
+
 # input history buffer, to store previous commands. 
 # we use a list [buffer, pointer] to avoid namespace pollution
 inbuf_history = [[], -1]
@@ -526,7 +586,30 @@ def stdin_read():
 	
 	in_esc = 0
 	input = sys.stdin.read()
+
+	global matchc_last
+	global matchc_status
+	global matchc_root
+	global matchl_status
+	global matchm_status
+	global matchm_root
+	global matchp_status
+	global matchp_root
+
 	for char in input:
+		# decrease the flag of the tab completion of commands
+		if matchc_status != 0:
+			matchc_status = matchc_status - 1
+		# decrease the flag of the last received/last send completion
+		elif matchl_status != 0:
+			matchl_status = matchl_status - 1
+		# decrease the flag of the m completion
+		elif matchm_status != 0:
+			matchm_status = matchm_status - 1
+		# decrease the flag of the other arguments completion
+		elif matchp_status != 0:
+			matchp_status = matchp_status - 1
+
 		if char == '\r':
 			# replace \r with \n, so we handle mac keyboard input
 			# properly (it breaks \r\n tho, but nobody uses it)
@@ -538,6 +621,7 @@ def stdin_read():
 				del(inbuf_history[0][0])
 			inbuf_history[0].append(inbuf[:-1])
 			inbuf_history[1] = len(inbuf_history[0]) - 1 # moves the pointer
+			start_from = 0 		# reset tab completion
 			
 			safe_write(char)
 			tmpbuf = inbuf
@@ -555,10 +639,14 @@ def stdin_read():
 			redraw_cli()
 			
 		elif char == '\t':				# tab
+			p = inbuf.split()
+
 			# we do a basic cycling between the last received and
 			# last sent; first we build the two strings and then
 			# we see which one applies according to some messy
 			# logic
+			# FIXME: it fails if we haven't in our contact list
+			# the person with we are talking
 			if email2nick(last_received):
 				nick = email2nick(last_received)
 				if ' ' in nick:
@@ -574,52 +662,104 @@ def stdin_read():
 				mtolsent = 'm ' + nick + ' '
 			else:
 				mtolsent = None
-			
+
+			# in an empty buffer we fill with the last received or
+			# the last sent
 			if len(inbuf) == 1:
 				if mtolsent:
 					inbuf = mtolsent
+					matchl_status = 2
 				elif mtolrecv:
 					inbuf = mtolrecv
+					matchl_status = 2
 				else:
 					inbuf = inbuf[:-1]
 					beep()
+
+			# if in the last cycle we have replaced with
+			# mtolsent or mtolrecv we try to fill ciclical
+			elif mtolsent and mtolrecv and matchl_status == 1:
+				if inbuf.strip() == mtolrecv.strip():
+					inbuf = mtolsent
+					matchl_status = 2
+				else:
+					inbuf = mtolrecv
+					matchl_status = 2
+
+			# temporarily if not mtolsent or mtolrecv we beep
+			# FIXME it do nothing if there's only mtolrecv and
+			# it changes between two tabs
+			elif matchl_status == 1:
+				# it avoids that in the next iteration the
+				# empty buffer completion will be taked for
+				# an m completion
+				matchl_status = 2
+				inbuf = inbuf[:-1]
+				beep()
+
+			# we have something that is neither mtolsent or
+			# mtolrecv, if is the m command we try to find a
+   			# matching email/nick
+			elif p[0] == 'm' and len(p) == 2:
+				begin = p[1]
+				if matchm_status == 1:
+					begin = matchm_root
+
+				# we try to match with onlines contacts
+				# and contacts with sbd
+				email = matchemail(begin, 1, matchm_status)
+				if not email:
+					inbuf = inbuf[:-1]
+					beep()
+				else:
+					matchm_root = begin
+					matchm_status = 2
+					inbuf = 'm ' + email + ' '
+
+			# if there's an only word buffer we try to match
+			# with one of the commands
+			elif len(p) == 1:
+				# if it's the 2nd tab we build a ciclical
+				# matching; if not, we remember the last match
+				if matchc_status == 1:
+					p[0] = matchc_root
+					matchc_last = matchc_last + 1
+
+				found = 1
+				while found or matchc_last != len(command_list):
+					if matchc_last == len(command_list):
+						matchc_last = 0
+						found = 0
+						break
+					elif command_list[matchc_last].find(p[0]) == 0:
+						matchc_status = 2
+						matchc_root = p[0]
+						break
+					matchc_last = matchc_last + 1
+				if matchc_last == len(command_list):
+					inbuf = inbuf[:-1]
+					beep()
+				else:
+					inbuf = command_list[matchc_last] + ' '
+
 			else:
-				# if we have mtolsent, replace with mtolrecv
-				# (if possible, otherwise beep)
-				if mtolsent and inbuf.strip() == mtolsent.strip():
-					if mtolrecv:
-						inbuf = mtolrecv
-					else:
-						inbuf = inbuf[:-1]
-						beep()
-				# the opposite case
-				elif mtolrecv and inbuf.strip() == mtolrecv.strip():
-					if mtolsent:
-						inbuf = mtolsent
-					else:
-						inbuf = inbuf[:-1]
-						beep()
-				# we have something that is neither mtolsent or
-				# mtolrecv, we try to find a matching
-				# email/nick
+				pn = p[len(p) - 1]
+				if matchp_status == 1:
+					pn = matchp_root
+
+				# we try to match with all of contacts
+				email = matchemail(pn, 0, matchp_status)
+				if not email:
+					inbuf = inbuf[:-1]
+					beep()
 				else:
-					p = inbuf.split()
-					if len(p) < 2:
-						# space + TAB or equivalent,
-						# just beep and ignore it
-						inbuf = inbuf[:-1]
-						beep()
-					elif p[0] != 'm' or len(p) != 2:
-						inbuf = inbuf[:-1]
-						beep()
-					else:
-						begin = p[1]
-						email = matchemail(begin)
-						if not email:
-							inbuf = inbuf[:-1]
-							beep()
-						else:
-							inbuf = 'm ' + email + ' '
+					matchp_root = pn
+					matchp_status = 2
+					p[len(p) -1] = email
+					inbuf = ''
+					for pi in p:
+						inbuf = inbuf + pi + ' '
+
 			redraw_cli()
 			
 		elif ord(char) == 4:				# EOT