xP: support adding formatting from keyboard
Just like in xC, only with some indication.
This commit is contained in:
		
							
								
								
									
										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;
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 
 | 
				
			|||||||
@@ -687,34 +687,25 @@ let BufferContainer = {
 | 
				
			|||||||
}
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let Toolbar = {
 | 
					let Toolbar = {
 | 
				
			||||||
	insert: formatting => {
 | 
						format: formatting => {
 | 
				
			||||||
		let textarea = document.getElementById('input')
 | 
							let textarea = document.getElementById('input')
 | 
				
			||||||
		if (textarea === null)
 | 
							if (textarea !== null)
 | 
				
			||||||
			return
 | 
								Input.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()
 | 
					 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	view: vnode => {
 | 
						view: vnode => {
 | 
				
			||||||
		let indicator = undefined
 | 
							let indicators = []
 | 
				
			||||||
		if (bufferLog === undefined && !bufferAutoscroll)
 | 
							if (bufferLog === undefined && !bufferAutoscroll)
 | 
				
			||||||
			indicator = m('.indicator', {}, '⇩')
 | 
								indicators.push(m('.indicator', {}, '⇩'))
 | 
				
			||||||
 | 
							if (Input.formatting)
 | 
				
			||||||
 | 
								indicators.push(m('.indicator', {}, '#'))
 | 
				
			||||||
		return m('.toolbar', {}, [
 | 
							return m('.toolbar', {}, [
 | 
				
			||||||
			indicator,
 | 
								indicators,
 | 
				
			||||||
			m('button', {onclick: event => Toolbar.insert('\u0002')},
 | 
								m('button', {onclick: event => Toolbar.format('\u0002')},
 | 
				
			||||||
				m('b', {}, 'B')),
 | 
									m('b', {}, 'B')),
 | 
				
			||||||
			m('button', {onclick: event => Toolbar.insert('\u001D')},
 | 
								m('button', {onclick: event => Toolbar.format('\u001D')},
 | 
				
			||||||
				m('i', {}, 'I')),
 | 
									m('i', {}, 'I')),
 | 
				
			||||||
			m('button', {onclick: event => Toolbar.insert('\u001F')},
 | 
								m('button', {onclick: event => Toolbar.format('\u001F')},
 | 
				
			||||||
				m('u', {}, 'U')),
 | 
									m('u', {}, 'U')),
 | 
				
			||||||
			m('button', {onclick: event => bufferToggleLog()},
 | 
								m('button', {onclick: event => bufferToggleLog()},
 | 
				
			||||||
				bufferLog === undefined ? 'Log' : 'Hide log'),
 | 
									bufferLog === undefined ? 'Log' : 'Hide log'),
 | 
				
			||||||
@@ -924,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'})
 | 
				
			||||||
@@ -935,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
 | 
				
			||||||
@@ -947,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 &&
 | 
				
			||||||
@@ -964,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,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
}
 | 
					}
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user