Compare commits
	
		
			3 Commits
		
	
	
		
			e6bf88673f
			...
			f2d8de3ab9
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| f2d8de3ab9 | |||
| 67d52a2d89 | |||
| ef3d1cc409 | 
							
								
								
									
										9
									
								
								xC.c
									
									
									
									
									
								
							
							
						
						
									
										9
									
								
								xC.c
									
									
									
									
									
								
							| @ -15010,21 +15010,28 @@ process_formatting_escape (const struct pollfd *fd, struct app_context *ctx) | |||||||
| 
 | 
 | ||||||
| 	if (buf->len != 1) | 	if (buf->len != 1) | ||||||
| 		goto error; | 		goto error; | ||||||
|  | 
 | ||||||
|  | 	// Letters mostly taken from their caret escapes + HTML element names.
 | ||||||
|  | 	// Additionally, 'm' stands for mono, 'x' for cross, 'r' for reset.
 | ||||||
| 	switch (buf->str[0]) | 	switch (buf->str[0]) | ||||||
| 	{ | 	{ | ||||||
| 	case 'b' ^ 96: | 	case 'b' ^ 96: | ||||||
| 	case 'b': CALL_ (ctx->input, insert, "\x02"); break; | 	case 'b': CALL_ (ctx->input, insert, "\x02"); break; | ||||||
| 	case 'c': CALL_ (ctx->input, insert, "\x03"); break; | 	case 'c': CALL_ (ctx->input, insert, "\x03"); break; | ||||||
|  | 	case 'q': | ||||||
|  | 	case 'm': CALL_ (ctx->input, insert, "\x11"); break; | ||||||
|  | 	case 'v': CALL_ (ctx->input, insert, "\x16"); break; | ||||||
| 	case 'i' ^ 96: | 	case 'i' ^ 96: | ||||||
| 	case 'i': | 	case 'i': | ||||||
| 	case ']': CALL_ (ctx->input, insert, "\x1d"); break; | 	case ']': CALL_ (ctx->input, insert, "\x1d"); break; | ||||||
|  | 	case 's' ^ 96: | ||||||
|  | 	case 's': | ||||||
| 	case 'x' ^ 96: | 	case 'x' ^ 96: | ||||||
| 	case 'x': | 	case 'x': | ||||||
| 	case '^': CALL_ (ctx->input, insert, "\x1e"); break; | 	case '^': CALL_ (ctx->input, insert, "\x1e"); break; | ||||||
| 	case 'u' ^ 96: | 	case 'u' ^ 96: | ||||||
| 	case 'u': | 	case 'u': | ||||||
| 	case '_': CALL_ (ctx->input, insert, "\x1f"); break; | 	case '_': CALL_ (ctx->input, insert, "\x1f"); break; | ||||||
| 	case 'v': CALL_ (ctx->input, insert, "\x16"); break; |  | ||||||
| 	case 'r': | 	case 'r': | ||||||
| 	case 'o': CALL_ (ctx->input, insert, "\x0f"); break; | 	case 'o': CALL_ (ctx->input, insert, "\x0f"); break; | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -69,12 +69,16 @@ body { | |||||||
| .toolbar { | .toolbar { | ||||||
| 	display: flex; | 	display: flex; | ||||||
| 	align-items: baseline; | 	align-items: baseline; | ||||||
| 	column-gap: .3em; | 	margin-right: -.3em; | ||||||
|  | } | ||||||
|  | .indicator { | ||||||
|  | 	margin: 0 .3em; | ||||||
| } | } | ||||||
| button { | button { | ||||||
| 	font: inherit; | 	font: inherit; | ||||||
| 	background: transparent; | 	background: transparent; | ||||||
| 	border: 1px solid transparent; | 	border: 1px solid transparent; | ||||||
|  | 	padding: 0 .3em; | ||||||
| } | } | ||||||
| button:focus { | button:focus { | ||||||
| 	border: 1px dashed #000; | 	border: 1px dashed #000; | ||||||
|  | |||||||
| @ -435,7 +435,7 @@ for (let i = 0; i < 24; i++) { | |||||||
| let linkRE = [ | let linkRE = [ | ||||||
| 	/https?:\/\//, | 	/https?:\/\//, | ||||||
| 	/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, | 	/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/, | ||||||
| 	/[^\[\](){}<>"'\s,.:]/, | 	/([^\[\](){}<>"'\s,.:]|\([^\[\](){}<>"'\s]*\))/, | ||||||
| ].map(r => r.source).join('') | ].map(r => r.source).join('') | ||||||
| 
 | 
 | ||||||
| let BufferList = { | let BufferList = { | ||||||
| @ -631,7 +631,7 @@ let Buffer = { | |||||||
| 		return m('.buffer', {onscroll: event => { | 		return m('.buffer', {onscroll: event => { | ||||||
| 			const dom = event.target | 			const dom = event.target | ||||||
| 			bufferAutoscroll = | 			bufferAutoscroll = | ||||||
| 				dom.scrollTop + dom.clientHeight + 0.5 >= dom.scrollHeight | 				dom.scrollTop + dom.clientHeight + 1 >= dom.scrollHeight | ||||||
| 		}}, lines) | 		}}, lines) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
| @ -687,9 +687,26 @@ let BufferContainer = { | |||||||
| } | } | ||||||
| 
 | 
 | ||||||
| let Toolbar = { | let Toolbar = { | ||||||
|  | 	format: formatting => { | ||||||
|  | 		let textarea = document.getElementById('input') | ||||||
|  | 		if (textarea !== null) | ||||||
|  | 			Input.format(textarea, formatting) | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	view: vnode => { | 	view: vnode => { | ||||||
|  | 		let indicators = [] | ||||||
|  | 		if (bufferLog === undefined && !bufferAutoscroll) | ||||||
|  | 			indicators.push(m('.indicator', {}, '⇩')) | ||||||
|  | 		if (Input.formatting) | ||||||
|  | 			indicators.push(m('.indicator', {}, '#')) | ||||||
| 		return m('.toolbar', {}, [ | 		return m('.toolbar', {}, [ | ||||||
| 			bufferLog === undefined && !bufferAutoscroll ? '⇩' : undefined, | 			indicators, | ||||||
|  | 			m('button', {onclick: event => Toolbar.format('\u0002')}, | ||||||
|  | 				m('b', {}, 'B')), | ||||||
|  | 			m('button', {onclick: event => Toolbar.format('\u001D')}, | ||||||
|  | 				m('i', {}, 'I')), | ||||||
|  | 			m('button', {onclick: event => Toolbar.format('\u001F')}, | ||||||
|  | 				m('u', {}, 'U')), | ||||||
| 			m('button', {onclick: event => bufferToggleLog()}, | 			m('button', {onclick: event => bufferToggleLog()}, | ||||||
| 				bufferLog === undefined ? 'Log' : 'Hide log'), | 				bufferLog === undefined ? 'Log' : 'Hide log'), | ||||||
| 		]) | 		]) | ||||||
| @ -898,6 +915,21 @@ let Input = { | |||||||
| 		return true | 		return true | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | 	formatting: false, | ||||||
|  | 
 | ||||||
|  | 	format: (textarea, formatting) => { | ||||||
|  | 		const [start, end] = [textarea.selectionStart, textarea.selectionEnd] | ||||||
|  | 		if (start === end) { | ||||||
|  | 			textarea.setRangeText(formatting) | ||||||
|  | 			textarea.setSelectionRange( | ||||||
|  | 				start + formatting.length, end + formatting.length) | ||||||
|  | 		} else { | ||||||
|  | 			textarea.setRangeText( | ||||||
|  | 				formatting + textarea.value.substr(start, end) + formatting) | ||||||
|  | 		} | ||||||
|  | 		textarea.focus() | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	onKeyDown: event => { | 	onKeyDown: event => { | ||||||
| 		// TODO: And perhaps on other actions, too.
 | 		// TODO: And perhaps on other actions, too.
 | ||||||
| 		rpc.send({command: 'Active'}) | 		rpc.send({command: 'Active'}) | ||||||
| @ -909,7 +941,29 @@ let Input = { | |||||||
| 		let textarea = event.currentTarget | 		let textarea = event.currentTarget | ||||||
| 		let handled = false | 		let handled = false | ||||||
| 		let success = true | 		let success = true | ||||||
| 		if (hasShortcutModifiers(event)) { | 		if (Input.formatting) { | ||||||
|  | 			Input.formatting = false | ||||||
|  | 
 | ||||||
|  | 			// Like process_formatting_escape() within xC.
 | ||||||
|  | 			handled = true | ||||||
|  | 			switch (event.key) { | ||||||
|  | 			case 'b': Input.format(textarea, '\u0002'); break | ||||||
|  | 			case 'c': Input.format(textarea, '\u0003'); break | ||||||
|  | 			case 'q': | ||||||
|  | 			case 'm': Input.format(textarea, '\u0011'); break | ||||||
|  | 			case 'v': Input.format(textarea, '\u0016'); break | ||||||
|  | 			case 'i': | ||||||
|  | 			case ']': Input.format(textarea, '\u001D'); break | ||||||
|  | 			case 's': | ||||||
|  | 			case 'x': | ||||||
|  | 			case '^': Input.format(textarea, '\u001E'); break | ||||||
|  | 			case 'u': | ||||||
|  | 			case '_': Input.format(textarea, '\u001F'); break | ||||||
|  | 			case 'r': | ||||||
|  | 			case 'o': Input.format(textarea, '\u000F'); break | ||||||
|  | 			default: success = false | ||||||
|  | 			} | ||||||
|  | 		} else if (hasShortcutModifiers(event)) { | ||||||
| 			handled = true | 			handled = true | ||||||
| 			switch (event.key) { | 			switch (event.key) { | ||||||
| 			case 'b': success = Input.backward(textarea);    break | 			case 'b': success = Input.backward(textarea);    break | ||||||
| @ -921,6 +975,7 @@ let Input = { | |||||||
| 			case '>': success = Input.last(b, textarea);     break | 			case '>': success = Input.last(b, textarea);     break | ||||||
| 			case 'p': success = Input.previous(b, textarea); break | 			case 'p': success = Input.previous(b, textarea); break | ||||||
| 			case 'n': success = Input.next(b, textarea);     break | 			case 'n': success = Input.next(b, textarea);     break | ||||||
|  | 			case 'm': success = Input.formatting = true;     break | ||||||
| 			default:  handled = false | 			default:  handled = false | ||||||
| 			} | 			} | ||||||
| 		} else if (!event.altKey && !event.ctrlKey && !event.metaKey && | 		} else if (!event.altKey && !event.ctrlKey && !event.metaKey && | ||||||
| @ -938,15 +993,20 @@ let Input = { | |||||||
| 			event.preventDefault() | 			event.preventDefault() | ||||||
| 	}, | 	}, | ||||||
| 
 | 
 | ||||||
|  | 	onStateChange: event => { | ||||||
|  | 		Completions.reset() | ||||||
|  | 		Input.formatting = false | ||||||
|  | 	}, | ||||||
|  | 
 | ||||||
| 	view: vnode => { | 	view: vnode => { | ||||||
| 		return m('textarea#input', { | 		return m('textarea#input', { | ||||||
| 			rows: 1, | 			rows: 1, | ||||||
| 			onkeydown: Input.onKeyDown, | 			onkeydown: Input.onKeyDown, | ||||||
| 			oninput: event => Completions.reset(), | 			oninput: Input.onStateChange, | ||||||
| 			// Sadly only supported in Firefox as of writing.
 | 			// Sadly only supported in Firefox as of writing.
 | ||||||
| 			onselectionchange: event => Completions.reset(), | 			onselectionchange: Input.onStateChange, | ||||||
| 			// The list of completions is scrollable without receiving focus.
 | 			// The list of completions is scrollable without receiving focus.
 | ||||||
| 			onblur: event => Completions.reset(), | 			onblur: Input.onStateChange, | ||||||
| 		}) | 		}) | ||||||
| 	}, | 	}, | ||||||
| } | } | ||||||
|  | |||||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user