Compare commits
	
		
			No commits in common. "2b13f891c9205e3bd0da7c562ee95d1b9b664e7d" and "93b66b6a2693ebbd8eb234a9436b457b0bdaba12" have entirely different histories.
		
	
	
		
			2b13f891c9
			...
			93b66b6a26
		
	
		
							
								
								
									
										50
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										50
									
								
								xC.c
									
									
									
									
									
								
							| @ -2868,7 +2868,6 @@ relay_try_fetch_client (struct app_context *ctx, int listen_fd) | |||||||
| 		if (accept_error_is_transient (errno)) | 		if (accept_error_is_transient (errno)) | ||||||
| 			print_warning ("%s: %s", "accept", strerror (errno)); | 			print_warning ("%s: %s", "accept", strerror (errno)); | ||||||
| 		else | 		else | ||||||
| 			// TODO: Rather dispose of the listening socket.
 |  | ||||||
| 			print_fatal ("%s: %s", "accept", strerror (errno)); | 			print_fatal ("%s: %s", "accept", strerror (errno)); | ||||||
| 		return true; | 		return true; | ||||||
| 	} | 	} | ||||||
| @ -12054,23 +12053,6 @@ handle_command_plugin (struct handler_args *a) | |||||||
| 	return true; | 	return true; | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| static bool |  | ||||||
| handle_command_relay (struct handler_args *a) |  | ||||||
| { |  | ||||||
| 	if (*a->arguments) |  | ||||||
| 		return false; |  | ||||||
| 
 |  | ||||||
| 	int len = 0; |  | ||||||
| 	LIST_FOR_EACH (struct client, c, a->ctx->clients) |  | ||||||
| 		len++; |  | ||||||
| 
 |  | ||||||
| 	if (a->ctx->relay_fd == -1) |  | ||||||
| 		log_global_status (a->ctx, "The relay is not enabled"); |  | ||||||
| 	else |  | ||||||
| 		log_global_status (a->ctx, "The relay has #d clients", len); |  | ||||||
| 	return true; |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| static bool | static bool | ||||||
| show_aliases_list (struct app_context *ctx) | show_aliases_list (struct app_context *ctx) | ||||||
| { | { | ||||||
| @ -12807,9 +12789,6 @@ g_command_handlers[] = | |||||||
| 	{ "plugin",     "Manage plugins", | 	{ "plugin",     "Manage plugins", | ||||||
| 	  "list | load <name> | unload <name>", | 	  "list | load <name> | unload <name>", | ||||||
| 	  handle_command_plugin,     0 }, | 	  handle_command_plugin,     0 }, | ||||||
| 	{ "relay",      "Show relay information", |  | ||||||
| 	  NULL, |  | ||||||
| 	  handle_command_relay,      0 }, |  | ||||||
| 
 | 
 | ||||||
| 	{ "alias",      "List or set aliases", | 	{ "alias",      "List or set aliases", | ||||||
| 	  "[<name> <definition>]", | 	  "[<name> <definition>]", | ||||||
| @ -13908,27 +13887,22 @@ build_editor_command (struct app_context *ctx, const char *filename) | |||||||
| 		{ | 		{ | ||||||
| 		case 'F': | 		case 'F': | ||||||
| 			str_append (&argument, filename); | 			str_append (&argument, filename); | ||||||
| 			continue; | 			break; | ||||||
| 		case 'L': | 		case 'L': | ||||||
| 			str_append_printf (&argument, "%zu", line_one_based); | 			str_append_printf (&argument, "%zu", line_one_based); | ||||||
| 			continue; | 			break; | ||||||
| 		case 'C': | 		case 'C': | ||||||
| 			str_append_printf (&argument, "%zu", column + 1); | 			str_append_printf (&argument, "%zu", column + 1); | ||||||
| 			continue; | 			break; | ||||||
| 		case 'B': | 		case 'B': | ||||||
| 			str_append_printf (&argument, "%d",  cursor + 1); | 			str_append_printf (&argument, "%d",  cursor + 1); | ||||||
| 			continue; | 			break; | ||||||
| 		case '%': | 		case '%': | ||||||
| 		case ' ': | 		case ' ': | ||||||
| 			str_append_c (&argument, *editor); | 			str_append_c (&argument, *editor); | ||||||
| 			continue; | 			break; | ||||||
| 		} | 		default: | ||||||
| 
 | 			print_warning ("unknown substitution variable"); | ||||||
| 		const char *p = editor; |  | ||||||
| 		if (soft_assert (utf8_decode (&p, strlen (p)) > 0)) |  | ||||||
| 		{ |  | ||||||
| 			log_global_error (ctx, "Unknown substitution variable: %#&s", |  | ||||||
| 				xstrndup (editor, p - editor)); |  | ||||||
| 		} | 		} | ||||||
| 	} | 	} | ||||||
| 	if (argument.len) | 	if (argument.len) | ||||||
| @ -14781,8 +14755,7 @@ try_reap_child (struct app_context *ctx) | |||||||
| 	if (WIFSTOPPED (status)) | 	if (WIFSTOPPED (status)) | ||||||
| 	{ | 	{ | ||||||
| 		// We could also send SIGCONT but what's the point
 | 		// We could also send SIGCONT but what's the point
 | ||||||
| 		log_global_debug (ctx, | 		print_debug ("a child has been stopped, killing its process group"); | ||||||
| 			"A child has been stopped, killing its process group"); |  | ||||||
| 		kill (-zombie, SIGKILL); | 		kill (-zombie, SIGKILL); | ||||||
| 		return true; | 		return true; | ||||||
| 	} | 	} | ||||||
| @ -15280,7 +15253,7 @@ client_process_message (struct client *c, | |||||||
| 	if (!relay_command_message_deserialize (m, r) | 	if (!relay_command_message_deserialize (m, r) | ||||||
| 	 || msg_unpacker_get_available (r)) | 	 || msg_unpacker_get_available (r)) | ||||||
| 	{ | 	{ | ||||||
| 		log_global_error (c->ctx, "Deserialization failed, killing client"); | 		print_error ("deserialization failed, killing client"); | ||||||
| 		return false; | 		return false; | ||||||
| 	} | 	} | ||||||
| 
 | 
 | ||||||
| @ -15299,8 +15272,7 @@ client_process_message (struct client *c, | |||||||
| 		if (m->data.hello.version != RELAY_VERSION) | 		if (m->data.hello.version != RELAY_VERSION) | ||||||
| 		{ | 		{ | ||||||
| 			// TODO: This should send back an error message and shut down.
 | 			// TODO: This should send back an error message and shut down.
 | ||||||
| 			log_global_error (c->ctx, | 			print_error ("protocol version mismatch, killing client"); | ||||||
| 				"Protocol version mismatch, killing client"); |  | ||||||
| 			return false; | 			return false; | ||||||
| 		} | 		} | ||||||
| 		c->initialized = true; | 		c->initialized = true; | ||||||
| @ -15328,7 +15300,7 @@ client_process_message (struct client *c, | |||||||
| 		client_process_buffer_log (c, m->command_seq, buffer); | 		client_process_buffer_log (c, m->command_seq, buffer); | ||||||
| 		break; | 		break; | ||||||
| 	default: | 	default: | ||||||
| 		log_global_debug (c->ctx, "Unhandled client command"); | 		print_warning ("unhandled client command"); | ||||||
| 		relay_prepare_error (c->ctx, m->command_seq, "Unknown command"); | 		relay_prepare_error (c->ctx, m->command_seq, "Unknown command"); | ||||||
| 		relay_send (c); | 		relay_send (c); | ||||||
| 	} | 	} | ||||||
|  | |||||||
| @ -16,10 +16,6 @@ body { | |||||||
| 	border-bottom: 1px solid #ccc; | 	border-bottom: 1px solid #ccc; | ||||||
| 	padding: .05rem .3rem; | 	padding: .05rem .3rem; | ||||||
| } | } | ||||||
| .title { |  | ||||||
| 	display: flex; |  | ||||||
| 	justify-content: space-between; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .middle { | .middle { | ||||||
| 	flex: auto; | 	flex: auto; | ||||||
| @ -56,12 +52,6 @@ body { | |||||||
| 	grid-template-columns: max-content auto; | 	grid-template-columns: max-content auto; | ||||||
| 	overflow-y: auto; | 	overflow-y: auto; | ||||||
| } | } | ||||||
| .log { |  | ||||||
| 	padding: .1rem .3rem; |  | ||||||
| 	font-family: monospace; |  | ||||||
| 	white-space: pre-wrap; |  | ||||||
| 	overflow-y: auto; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| .date { | .date { | ||||||
| 	padding: .3rem; | 	padding: .3rem; | ||||||
|  | |||||||
							
								
								
									
										270
									
								
								xP/public/xP.js
									
									
									
									
									
								
							
							
						
						
									
										270
									
								
								xP/public/xP.js
									
									
									
									
									
								
							| @ -118,11 +118,6 @@ class RelayRpc extends EventTarget { | |||||||
| 			this.promised[seq] = {resolve, reject} | 			this.promised[seq] = {resolve, reject} | ||||||
| 		}) | 		}) | ||||||
| 	} | 	} | ||||||
| 
 |  | ||||||
| 	base64decode(str) { |  | ||||||
| 		return decodeURIComponent(atob(str).split('').map(c => |  | ||||||
| 			'%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2)).join('')) |  | ||||||
| 	} |  | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ---- Event processing -------------------------------------------------------
 | // ---- Event processing -------------------------------------------------------
 | ||||||
| @ -131,16 +126,10 @@ let rpc = new RelayRpc(proxy) | |||||||
| 
 | 
 | ||||||
| let buffers = new Map() | let buffers = new Map() | ||||||
| let bufferCurrent = undefined | let bufferCurrent = undefined | ||||||
| let bufferLog = undefined |  | ||||||
| let bufferAutoscroll = true |  | ||||||
| 
 |  | ||||||
| let connecting = true | let connecting = true | ||||||
| rpc.connect().then(result => { | rpc.connect().then(result => { | ||||||
| 	buffers.clear() | 	buffers.clear() | ||||||
| 	bufferCurrent = undefined | 	bufferCurrent = undefined | ||||||
| 	bufferLog = undefined |  | ||||||
| 	bufferAutoscroll = true |  | ||||||
| 
 |  | ||||||
| 	rpc.send({command: 'Hello', version: 1}) | 	rpc.send({command: 'Hello', version: 1}) | ||||||
| 	connecting = false | 	connecting = false | ||||||
| 	m.redraw() | 	m.redraw() | ||||||
| @ -174,24 +163,13 @@ rpc.addEventListener('BufferRemove', event => { | |||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferActivate', event => { | rpc.addEventListener('BufferActivate', event => { | ||||||
| 	let e = event.detail, b = buffers.get(e.bufferName) | 	let e = event.detail | ||||||
| 	let old = buffers.get(bufferCurrent) |  | ||||||
| 	bufferCurrent = e.bufferName | 	bufferCurrent = e.bufferName | ||||||
| 	bufferLog = undefined | 	setTimeout(() => { | ||||||
| 	bufferAutoscroll = true | 		let el = document.getElementById('input') | ||||||
| 
 | 		if (el !== null) | ||||||
| 	let textarea = document.getElementById('input') | 			el.focus() | ||||||
| 	if (textarea === null) | 	}) | ||||||
| 		return |  | ||||||
| 
 |  | ||||||
| 	textarea.focus() |  | ||||||
| 	if (old !== undefined) |  | ||||||
| 		old.input = textarea.value |  | ||||||
| 
 |  | ||||||
| 	if (b !== undefined) |  | ||||||
| 		textarea.value = b.input || '' |  | ||||||
| 	else |  | ||||||
| 		textarea.value = '' |  | ||||||
| }) | }) | ||||||
| 
 | 
 | ||||||
| rpc.addEventListener('BufferLine', event => { | rpc.addEventListener('BufferLine', event => { | ||||||
| @ -226,56 +204,7 @@ for (let i = 0; i < 24; i++) { | |||||||
| 	palette[232 + i] = `#${g}${g}${g}` | 	palette[232 + i] = `#${g}${g}${g}` | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| // ---- UI ---------------------------------------------------------------------
 | function applyColor(fg, bg, inverse) { | ||||||
| 
 |  | ||||||
| let Toolbar = { |  | ||||||
| 	toggleAutoscroll: () => { |  | ||||||
| 		bufferAutoscroll = !bufferAutoscroll |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	toggleLog: () => { |  | ||||||
| 		if (bufferLog) { |  | ||||||
| 			bufferLog = undefined |  | ||||||
| 			return |  | ||||||
| 		} |  | ||||||
| 
 |  | ||||||
| 		rpc.send({ |  | ||||||
| 			command: 'BufferLog', |  | ||||||
| 			bufferName: bufferCurrent, |  | ||||||
| 		}).then(resp => { |  | ||||||
| 			bufferLog = rpc.base64decode(resp.log) |  | ||||||
| 			m.redraw() |  | ||||||
| 		}) |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	view: vnode => { |  | ||||||
| 		return m('.toolbar', {}, [ |  | ||||||
| 			m('button', {onclick: Toolbar.toggleAutoscroll}, |  | ||||||
| 				bufferAutoscroll ? 'Pause autoscroll' : 'Unpause autoscroll'), |  | ||||||
| 			m('button', {onclick: Toolbar.toggleLog}, |  | ||||||
| 				bufferLog === undefined ? 'Show log' : 'Hide log'), |  | ||||||
| 		]) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let BufferList = { |  | ||||||
| 	activate: name => { |  | ||||||
| 		rpc.send({command: 'BufferActivate', bufferName: name}) |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	view: vnode => { |  | ||||||
| 		let items = Array.from(buffers, ([name, b]) => { |  | ||||||
| 			let attrs = {onclick: event => BufferList.activate(name)} |  | ||||||
| 			if (name == bufferCurrent) |  | ||||||
| 				attrs.class = 'active' |  | ||||||
| 			return m('.item', attrs, name) |  | ||||||
| 		}) |  | ||||||
| 		return m('.list', {}, items) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let Content = { |  | ||||||
| 	applyColor: (fg, bg, inverse) => { |  | ||||||
| 	if (inverse) | 	if (inverse) | ||||||
| 		[fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0] | 		[fg, bg] = [bg >= 0 ? bg : 15, fg >= 0 ? fg : 0] | ||||||
| 
 | 
 | ||||||
| @ -286,16 +215,35 @@ let Content = { | |||||||
| 		style.backgroundColor = palette[bg] | 		style.backgroundColor = palette[bg] | ||||||
| 	if (style) | 	if (style) | ||||||
| 		return style | 		return style | ||||||
| 	}, | } | ||||||
| 
 | 
 | ||||||
| 	linkify: (text, attrs) => { | // ---- UI ---------------------------------------------------------------------
 | ||||||
|  | 
 | ||||||
|  | let BufferList = { | ||||||
|  | 	view: vnode => { | ||||||
|  | 		let items = [] | ||||||
|  | 		buffers.forEach((b, name) => { | ||||||
|  | 			let attrs = { | ||||||
|  | 				onclick: event => { | ||||||
|  | 					rpc.send({command: 'BufferActivate', bufferName: name}) | ||||||
|  | 				}, | ||||||
|  | 			} | ||||||
|  | 			if (name == bufferCurrent) | ||||||
|  | 				attrs.class = 'active' | ||||||
|  | 			items.push(m('.item', attrs, name)) | ||||||
|  | 		}) | ||||||
|  | 		return m('.list', {}, items) | ||||||
|  | 	}, | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | function linkify(text, attrs, a) { | ||||||
| 	let re = new RegExp([ | 	let re = new RegExp([ | ||||||
| 		/https?:\/\//, | 		/https?:\/\//, | ||||||
| 		/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, | 		/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, | ||||||
| 		/[^\[\](){}<>"'\s,.:]/, | 		/[^\[\](){}<>"'\s,.:]/, | ||||||
| 	].map(r => r.source).join(''), 'g') | 	].map(r => r.source).join(''), 'g') | ||||||
| 
 | 
 | ||||||
| 		let a = [], end = 0, match | 	let end = 0, match | ||||||
| 	while ((match = re.exec(text)) !== null) { | 	while ((match = re.exec(text)) !== null) { | ||||||
| 		if (end < match.index) | 		if (end < match.index) | ||||||
| 			a.push(m('span', attrs, text.substring(end, match.index))) | 			a.push(m('span', attrs, text.substring(end, match.index))) | ||||||
| @ -304,19 +252,19 @@ let Content = { | |||||||
| 	} | 	} | ||||||
| 	if (end < text.length) | 	if (end < text.length) | ||||||
| 		a.push(m('span', attrs, text.substring(end))) | 		a.push(m('span', attrs, text.substring(end))) | ||||||
| 		return a | } | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
|  | let Content = { | ||||||
| 	view: vnode => { | 	view: vnode => { | ||||||
| 		let line = vnode.children[0] | 		let line = vnode.children[0] | ||||||
| 		let mark = undefined | 		let content = [] | ||||||
| 		switch (line.rendition) { | 		switch (line.rendition) { | ||||||
| 		case 'Indent': mark = m('span.mark',        {}, '');  break | 		case 'Indent': content.push(m('span.mark',        {}, ''));  break | ||||||
| 		case 'Status': mark = m('span.mark',        {}, '–'); break | 		case 'Status': content.push(m('span.mark',        {}, '–')); break | ||||||
| 		case 'Error':  mark = m('span.mark.error',  {}, '⚠'); break | 		case 'Error':  content.push(m('span.mark.error',  {}, '⚠')); break | ||||||
| 		case 'Join':   mark = m('span.mark.join',   {}, '→'); break | 		case 'Join':   content.push(m('span.mark.join',   {}, '→')); break | ||||||
| 		case 'Part':   mark = m('span.mark.part',   {}, '←'); break | 		case 'Part':   content.push(m('span.mark.part',   {}, '←')); break | ||||||
| 		case 'Action': mark = m('span.mark.action', {}, '✶'); break | 		case 'Action': content.push(m('span.mark.action', {}, '✶')); break | ||||||
| 		} | 		} | ||||||
| 
 | 
 | ||||||
| 		let classes = new Set() | 		let classes = new Set() | ||||||
| @ -327,13 +275,14 @@ let Content = { | |||||||
| 				classes.add(c) | 				classes.add(c) | ||||||
| 		} | 		} | ||||||
| 		let fg = -1, bg = -1, inverse = false | 		let fg = -1, bg = -1, inverse = false | ||||||
| 		return m('.content', {}, [mark, line.items.flatMap(item => { | 		line.items.forEach(item => { | ||||||
| 			switch (item.kind) { | 			switch (item.kind) { | ||||||
| 			case 'Text': | 			case 'Text': | ||||||
| 				return Content.linkify(item.text, { | 				linkify(item.text, { | ||||||
| 					class: Array.from(classes.keys()).join(' '), | 					class: Array.from(classes.keys()).join(' '), | ||||||
| 					style: Content.applyColor(fg, bg, inverse), | 					style: applyColor(fg, bg, inverse), | ||||||
| 				}) | 				}, content) | ||||||
|  | 				break | ||||||
| 			case 'Reset': | 			case 'Reset': | ||||||
| 				classes.clear() | 				classes.clear() | ||||||
| 				fg = bg = -1 | 				fg = bg = -1 | ||||||
| @ -348,30 +297,25 @@ let Content = { | |||||||
| 			case 'FlipInverse': | 			case 'FlipInverse': | ||||||
| 				inverse = !inverse | 				inverse = !inverse | ||||||
| 				break | 				break | ||||||
| 			case 'FlipBold': | 			case 'FlipBold':       flip('b'); break | ||||||
| 				flip('b') | 			case 'FlipItalic':     flip('i'); break | ||||||
| 				break | 			case 'FlipUnderline':  flip('u'); break | ||||||
| 			case 'FlipItalic': | 			case 'FlipCrossedOut': flip('s'); break | ||||||
| 				flip('i') | 			case 'FlipMonospace':  flip('m'); break | ||||||
| 				break |  | ||||||
| 			case 'FlipUnderline': |  | ||||||
| 				flip('u') |  | ||||||
| 				break |  | ||||||
| 			case 'FlipCrossedOut': |  | ||||||
| 				flip('s') |  | ||||||
| 				break |  | ||||||
| 			case 'FlipMonospace': |  | ||||||
| 				flip('m') |  | ||||||
| 				break |  | ||||||
| 			} | 			} | ||||||
| 		})]) | 		}) | ||||||
|  | 		return m('.content', {}, content) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let Buffer = { | let Buffer = { | ||||||
| 	oncreate: vnode => { | 	oncreate: vnode => { | ||||||
| 		if (vnode.dom !== undefined && bufferAutoscroll) | 		if (vnode.dom === undefined) | ||||||
| 			vnode.dom.scrollTop = vnode.dom.scrollHeight | 			return | ||||||
|  | 
 | ||||||
|  | 		let el = vnode.dom.children[1] | ||||||
|  | 		if (el !== null) | ||||||
|  | 			el.scrollTop = el.scrollHeight | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
| 	onupdate: vnode => { | 	onupdate: vnode => { | ||||||
| @ -396,84 +340,62 @@ let Buffer = { | |||||||
| 			lines.push(m('.time', {}, date.toLocaleTimeString())) | 			lines.push(m('.time', {}, date.toLocaleTimeString())) | ||||||
| 			lines.push(m(Content, {}, line)) | 			lines.push(m(Content, {}, line)) | ||||||
| 		}) | 		}) | ||||||
| 		return m('.buffer', {}, lines) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let Log = { |  | ||||||
| 	oncreate: vnode => { |  | ||||||
| 		if (vnode.dom !== undefined) |  | ||||||
| 			vnode.dom.scrollTop = vnode.dom.scrollHeight |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	view: vnode => { |  | ||||||
| 		return m(".log", {}, bufferLog) |  | ||||||
| 	}, |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| let BufferContainer = { |  | ||||||
| 	view: vnode => { |  | ||||||
| 		return m('.buffer-container', {}, [ | 		return m('.buffer-container', {}, [ | ||||||
| 			m('.filler'), | 			m('.filler'), | ||||||
| 			bufferLog !== undefined ? m(Log) : m(Buffer), | 			m('.buffer', {}, lines), | ||||||
| 		]) | 		]) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let Input = { | function onKeyDown(event) { | ||||||
| 	complete: textarea => { |  | ||||||
| 		if (textarea.selectionStart !== textarea.selectionEnd) |  | ||||||
| 			return false |  | ||||||
| 
 |  | ||||||
| 		rpc.send({ |  | ||||||
| 			command: 'BufferComplete', |  | ||||||
| 			bufferName: bufferCurrent, |  | ||||||
| 			text: textarea.value, |  | ||||||
| 			position: textarea.selectionEnd, |  | ||||||
| 		}).then(resp => { |  | ||||||
| 			// TODO: Somehow display remaining options, or cycle through.
 |  | ||||||
| 			if (resp.completions.length) |  | ||||||
| 				textarea.setRangeText(resp.completions[0], |  | ||||||
| 					resp.start, textarea.selectionEnd, 'end') |  | ||||||
| 			if (resp.completions.length === 1) |  | ||||||
| 				textarea.setRangeText(' ', |  | ||||||
| 					textarea.selectionStart, textarea.selectionEnd, 'end') |  | ||||||
| 		}) |  | ||||||
| 		return true |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	submit: textarea => { |  | ||||||
| 		rpc.send({ |  | ||||||
| 			command: 'BufferInput', |  | ||||||
| 			bufferName: bufferCurrent, |  | ||||||
| 			text: textarea.value, |  | ||||||
| 		}) |  | ||||||
| 		textarea.value = '' |  | ||||||
| 		return true |  | ||||||
| 	}, |  | ||||||
| 
 |  | ||||||
| 	onKeyDown: event => { |  | ||||||
| 	// TODO: And perhaps on other actions, too.
 | 	// TODO: And perhaps on other actions, too.
 | ||||||
| 	rpc.send({command: 'Active'}) | 	rpc.send({command: 'Active'}) | ||||||
| 
 | 
 | ||||||
| 	// TODO: Cancel any current autocomplete.
 | 	// TODO: Cancel any current autocomplete.
 | ||||||
| 
 | 
 | ||||||
| 	let textarea = event.currentTarget | 	let textarea = event.currentTarget | ||||||
| 		let handled = false |  | ||||||
| 	switch (event.keyCode) { | 	switch (event.keyCode) { | ||||||
| 	case 9: | 	case 9: | ||||||
| 			handled = Input.complete(textarea) | 		if (textarea.selectionStart !== textarea.selectionEnd) | ||||||
| 			break | 			return | ||||||
|  | 		rpc.send({ | ||||||
|  | 			command: 'BufferComplete', | ||||||
|  | 			bufferName: bufferCurrent, | ||||||
|  | 			text: textarea.value, | ||||||
|  | 			position: textarea.selectionEnd, | ||||||
|  | 		}).then(response => { | ||||||
|  | 			// TODO: Somehow display remaining options, or cycle through.
 | ||||||
|  | 			if (response.completions.length) | ||||||
|  | 				textarea.setRangeText(response.completions[0], | ||||||
|  | 					response.start, textarea.selectionEnd, 'end') | ||||||
|  | 			if (response.completions.length === 1) | ||||||
|  | 				textarea.setRangeText(' ', | ||||||
|  | 					textarea.selectionStart, textarea.selectionEnd, 'end') | ||||||
|  | 		}) | ||||||
|  | 		break; | ||||||
| 	case 13: | 	case 13: | ||||||
| 			handled = Input.submit(textarea) | 		rpc.send({ | ||||||
| 			break | 			command: 'BufferInput', | ||||||
|  | 			bufferName: bufferCurrent, | ||||||
|  | 			text: textarea.value, | ||||||
|  | 		}) | ||||||
|  | 		textarea.value = '' | ||||||
|  | 		break; | ||||||
|  | 	default: | ||||||
|  | 		return | ||||||
| 	} | 	} | ||||||
| 		if (handled) |  | ||||||
| 			event.preventDefault() |  | ||||||
| 	}, |  | ||||||
| 
 | 
 | ||||||
|  | 	event.preventDefault() | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | // TODO: This should be remembered across buffer switches,
 | ||||||
|  | // and we'll probably have to intercept /all/ key presses.
 | ||||||
|  | let Input = { | ||||||
| 	view: vnode => { | 	view: vnode => { | ||||||
| 		return m('textarea#input', {rows: 1, onkeydown: Input.onKeyDown}) | 		return m('textarea#input', { | ||||||
|  | 			rows: 1, | ||||||
|  | 			onkeydown: onKeyDown, | ||||||
|  | 		}) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| @ -486,8 +408,8 @@ let Main = { | |||||||
| 			state = "Disconnected" | 			state = "Disconnected" | ||||||
| 
 | 
 | ||||||
| 		return m('.xP', {}, [ | 		return m('.xP', {}, [ | ||||||
| 			m('.title', {}, [`xP (${state})`, m(Toolbar)]), | 			m('.title', {}, `xP (${state})`), | ||||||
| 			m('.middle', {}, [m(BufferList), m(BufferContainer)]), | 			m('.middle', {}, [m(BufferList), m(Buffer)]), | ||||||
| 			m('.status', {}, bufferCurrent), | 			m('.status', {}, bufferCurrent), | ||||||
| 			m(Input), | 			m(Input), | ||||||
| 		]) | 		]) | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user