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)
 | 
			
		||||
		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])
 | 
			
		||||
	{
 | 
			
		||||
	case 'b' ^ 96:
 | 
			
		||||
	case 'b': CALL_ (ctx->input, insert, "\x02"); 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':
 | 
			
		||||
	case ']': CALL_ (ctx->input, insert, "\x1d"); break;
 | 
			
		||||
	case 's' ^ 96:
 | 
			
		||||
	case 's':
 | 
			
		||||
	case 'x' ^ 96:
 | 
			
		||||
	case 'x':
 | 
			
		||||
	case '^': CALL_ (ctx->input, insert, "\x1e"); break;
 | 
			
		||||
	case 'u' ^ 96:
 | 
			
		||||
	case 'u':
 | 
			
		||||
	case '_': CALL_ (ctx->input, insert, "\x1f"); break;
 | 
			
		||||
	case 'v': CALL_ (ctx->input, insert, "\x16"); break;
 | 
			
		||||
	case 'r':
 | 
			
		||||
	case 'o': CALL_ (ctx->input, insert, "\x0f"); break;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -687,34 +687,25 @@ let BufferContainer = {
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
let Toolbar = {
 | 
			
		||||
	insert: formatting => {
 | 
			
		||||
	format: formatting => {
 | 
			
		||||
		let textarea = document.getElementById('input')
 | 
			
		||||
		if (textarea === null)
 | 
			
		||||
			return
 | 
			
		||||
 | 
			
		||||
		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()
 | 
			
		||||
		if (textarea !== null)
 | 
			
		||||
			Input.format(textarea, formatting)
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	view: vnode => {
 | 
			
		||||
		let indicator = undefined
 | 
			
		||||
		let indicators = []
 | 
			
		||||
		if (bufferLog === undefined && !bufferAutoscroll)
 | 
			
		||||
			indicator = m('.indicator', {}, '⇩')
 | 
			
		||||
			indicators.push(m('.indicator', {}, '⇩'))
 | 
			
		||||
		if (Input.formatting)
 | 
			
		||||
			indicators.push(m('.indicator', {}, '#'))
 | 
			
		||||
		return m('.toolbar', {}, [
 | 
			
		||||
			indicator,
 | 
			
		||||
			m('button', {onclick: event => Toolbar.insert('\u0002')},
 | 
			
		||||
			indicators,
 | 
			
		||||
			m('button', {onclick: event => Toolbar.format('\u0002')},
 | 
			
		||||
				m('b', {}, 'B')),
 | 
			
		||||
			m('button', {onclick: event => Toolbar.insert('\u001D')},
 | 
			
		||||
			m('button', {onclick: event => Toolbar.format('\u001D')},
 | 
			
		||||
				m('i', {}, 'I')),
 | 
			
		||||
			m('button', {onclick: event => Toolbar.insert('\u001F')},
 | 
			
		||||
			m('button', {onclick: event => Toolbar.format('\u001F')},
 | 
			
		||||
				m('u', {}, 'U')),
 | 
			
		||||
			m('button', {onclick: event => bufferToggleLog()},
 | 
			
		||||
				bufferLog === undefined ? 'Log' : 'Hide log'),
 | 
			
		||||
@@ -924,6 +915,21 @@ let Input = {
 | 
			
		||||
		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 => {
 | 
			
		||||
		// TODO: And perhaps on other actions, too.
 | 
			
		||||
		rpc.send({command: 'Active'})
 | 
			
		||||
@@ -935,7 +941,29 @@ let Input = {
 | 
			
		||||
		let textarea = event.currentTarget
 | 
			
		||||
		let handled = false
 | 
			
		||||
		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
 | 
			
		||||
			switch (event.key) {
 | 
			
		||||
			case 'b': success = Input.backward(textarea);    break
 | 
			
		||||
@@ -947,6 +975,7 @@ let Input = {
 | 
			
		||||
			case '>': success = Input.last(b, textarea);     break
 | 
			
		||||
			case 'p': success = Input.previous(b, textarea); break
 | 
			
		||||
			case 'n': success = Input.next(b, textarea);     break
 | 
			
		||||
			case 'm': success = Input.formatting = true;     break
 | 
			
		||||
			default:  handled = false
 | 
			
		||||
			}
 | 
			
		||||
		} else if (!event.altKey && !event.ctrlKey && !event.metaKey &&
 | 
			
		||||
@@ -964,15 +993,20 @@ let Input = {
 | 
			
		||||
			event.preventDefault()
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	onStateChange: event => {
 | 
			
		||||
		Completions.reset()
 | 
			
		||||
		Input.formatting = false
 | 
			
		||||
	},
 | 
			
		||||
 | 
			
		||||
	view: vnode => {
 | 
			
		||||
		return m('textarea#input', {
 | 
			
		||||
			rows: 1,
 | 
			
		||||
			onkeydown: Input.onKeyDown,
 | 
			
		||||
			oninput: event => Completions.reset(),
 | 
			
		||||
			oninput: Input.onStateChange,
 | 
			
		||||
			// Sadly only supported in Firefox as of writing.
 | 
			
		||||
			onselectionchange: event => Completions.reset(),
 | 
			
		||||
			onselectionchange: Input.onStateChange,
 | 
			
		||||
			// The list of completions is scrollable without receiving focus.
 | 
			
		||||
			onblur: event => Completions.reset(),
 | 
			
		||||
			onblur: Input.onStateChange,
 | 
			
		||||
		})
 | 
			
		||||
	},
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user