xP: add basic buffer input history
Bind M-p and M-n as in xC. Also make all our bindings reachable on macOS.
This commit is contained in:
		@@ -197,7 +197,11 @@ rpc.addEventListener('Ping', event => {
 | 
				
			|||||||
rpc.addEventListener('BufferUpdate', event => {
 | 
					rpc.addEventListener('BufferUpdate', event => {
 | 
				
			||||||
	let e = event.detail, b = buffers.get(e.bufferName)
 | 
						let e = event.detail, b = buffers.get(e.bufferName)
 | 
				
			||||||
	if (b === undefined) {
 | 
						if (b === undefined) {
 | 
				
			||||||
		buffers.set(e.bufferName, (b = {lines: []}))
 | 
							buffers.set(e.bufferName, (b = {
 | 
				
			||||||
 | 
								lines: [],
 | 
				
			||||||
 | 
								history: [],
 | 
				
			||||||
 | 
								historyAt: 0,
 | 
				
			||||||
 | 
							}))
 | 
				
			||||||
		bufferResetStats(b)
 | 
							bufferResetStats(b)
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
	b.hideUnimportant = e.hideUnimportant
 | 
						b.hideUnimportant = e.hideUnimportant
 | 
				
			||||||
@@ -247,6 +251,9 @@ rpc.addEventListener('BufferActivate', event => {
 | 
				
			|||||||
		old.inputStart = textarea.selectionStart
 | 
							old.inputStart = textarea.selectionStart
 | 
				
			||||||
		old.inputEnd = textarea.selectionEnd
 | 
							old.inputEnd = textarea.selectionEnd
 | 
				
			||||||
		old.inputDirection = textarea.selectionDirection
 | 
							old.inputDirection = textarea.selectionDirection
 | 
				
			||||||
 | 
							// Note that we effectively overwrite the newest line
 | 
				
			||||||
 | 
							// with the current textarea contents, and jump there.
 | 
				
			||||||
 | 
							old.historyAt = old.history.length
 | 
				
			||||||
	}
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	textarea.value = ''
 | 
						textarea.value = ''
 | 
				
			||||||
@@ -324,6 +331,15 @@ for (let i = 0; i < 24; i++) {
 | 
				
			|||||||
 | 
					
 | 
				
			||||||
// ---- UI ---------------------------------------------------------------------
 | 
					// ---- UI ---------------------------------------------------------------------
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
					// On macOS, the Alt/Option key transforms characters, which basically breaks
 | 
				
			||||||
 | 
					// all event.altKey shortcuts, so require combining them with Control as well
 | 
				
			||||||
 | 
					// on that system.
 | 
				
			||||||
 | 
					function hasShortcutModifiers(event) {
 | 
				
			||||||
 | 
						// This method of detection only works with Blink browsers, as of writing.
 | 
				
			||||||
 | 
						return event.altKey && !event.metaKey &&
 | 
				
			||||||
 | 
							(navigator.userAgentData?.platform === 'macOS') === event.ctrlKey
 | 
				
			||||||
 | 
					}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
let linkRE = [
 | 
					let linkRE = [
 | 
				
			||||||
	/https?:\/\//,
 | 
						/https?:\/\//,
 | 
				
			||||||
	/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
 | 
						/([^\[\](){}<>"'\s]|\([^\[\](){}<>"'\s]*\))+/,
 | 
				
			||||||
@@ -584,27 +600,71 @@ let Input = {
 | 
				
			|||||||
			bufferName: bufferCurrent,
 | 
								bufferName: bufferCurrent,
 | 
				
			||||||
			text: textarea.value,
 | 
								text: textarea.value,
 | 
				
			||||||
		})
 | 
							})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// b.history[b.history.length] is virtual, and is represented
 | 
				
			||||||
 | 
							// either by textarea contents when it's currently being edited,
 | 
				
			||||||
 | 
							// or by b.input in all other cases.
 | 
				
			||||||
 | 
							let b = buffers.get(bufferCurrent)
 | 
				
			||||||
 | 
							b.history.push(textarea.value)
 | 
				
			||||||
 | 
							b.historyAt = b.history.length
 | 
				
			||||||
		textarea.value = ''
 | 
							textarea.value = ''
 | 
				
			||||||
		return true
 | 
							return true
 | 
				
			||||||
	},
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						previous: textarea => {
 | 
				
			||||||
 | 
							let b = buffers.get(bufferCurrent)
 | 
				
			||||||
 | 
							if (b === undefined)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Ding otherwise.
 | 
				
			||||||
 | 
							if (b.historyAt > 0) {
 | 
				
			||||||
 | 
								if (b.historyAt == b.history.length)
 | 
				
			||||||
 | 
									b.input = textarea.value
 | 
				
			||||||
 | 
								textarea.value = b.history[--b.historyAt]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
						next: textarea => {
 | 
				
			||||||
 | 
							let b = buffers.get(bufferCurrent)
 | 
				
			||||||
 | 
							if (b === undefined)
 | 
				
			||||||
 | 
								return false
 | 
				
			||||||
 | 
					
 | 
				
			||||||
 | 
							// TODO: Ding otherwise.
 | 
				
			||||||
 | 
							if (b.historyAt < b.history.length) {
 | 
				
			||||||
 | 
								if (++b.historyAt == b.history.length)
 | 
				
			||||||
 | 
									textarea.value = b.input
 | 
				
			||||||
 | 
								else
 | 
				
			||||||
 | 
									textarea.value = b.history[b.historyAt]
 | 
				
			||||||
 | 
							}
 | 
				
			||||||
 | 
							return true
 | 
				
			||||||
 | 
						},
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	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'})
 | 
				
			||||||
 | 
					
 | 
				
			||||||
		let textarea = event.currentTarget
 | 
							let textarea = event.currentTarget
 | 
				
			||||||
		let handled = false
 | 
							let handled = false
 | 
				
			||||||
		switch (event.keyCode) {
 | 
							if (hasShortcutModifiers(event)) {
 | 
				
			||||||
		case 9:
 | 
								switch (event.key) {
 | 
				
			||||||
			if (!event.ctrlKey && !event.metaKey && !event.altKey &&
 | 
								case 'p':
 | 
				
			||||||
				!event.shiftKey)
 | 
									handled = Input.previous(textarea)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								case 'n':
 | 
				
			||||||
 | 
									handled = Input.next(textarea)
 | 
				
			||||||
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
 | 
							} else if (!event.altKey && !event.ctrlKey && !event.metaKey &&
 | 
				
			||||||
 | 
									!event.shiftKey) {
 | 
				
			||||||
 | 
								switch (event.keyCode) {
 | 
				
			||||||
 | 
								case 9:
 | 
				
			||||||
				handled = Input.complete(textarea)
 | 
									handled = Input.complete(textarea)
 | 
				
			||||||
			break
 | 
									break
 | 
				
			||||||
		case 13:
 | 
								case 13:
 | 
				
			||||||
			if (!event.ctrlKey && !event.metaKey && !event.altKey &&
 | 
					 | 
				
			||||||
				!event.shiftKey)
 | 
					 | 
				
			||||||
				handled = Input.submit(textarea)
 | 
									handled = Input.submit(textarea)
 | 
				
			||||||
			break
 | 
									break
 | 
				
			||||||
 | 
								}
 | 
				
			||||||
		}
 | 
							}
 | 
				
			||||||
		if (handled)
 | 
							if (handled)
 | 
				
			||||||
			event.preventDefault()
 | 
								event.preventDefault()
 | 
				
			||||||
@@ -636,28 +696,34 @@ let Main = {
 | 
				
			|||||||
window.addEventListener('load', () => m.mount(document.body, Main))
 | 
					window.addEventListener('load', () => m.mount(document.body, Main))
 | 
				
			||||||
 | 
					
 | 
				
			||||||
document.addEventListener('keydown', event => {
 | 
					document.addEventListener('keydown', event => {
 | 
				
			||||||
	if (rpc.ws == undefined || event.ctrlKey || event.metaKey)
 | 
						if (rpc.ws == undefined || !hasShortcutModifiers(event))
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	if (event.altKey && event.key == 'Tab') {
 | 
						switch (event.key) {
 | 
				
			||||||
 | 
						case 'Tab':
 | 
				
			||||||
		if (bufferLast !== undefined)
 | 
							if (bufferLast !== undefined)
 | 
				
			||||||
			bufferActivate(bufferLast)
 | 
								bufferActivate(bufferLast)
 | 
				
			||||||
	} else if (event.altKey && event.key == 'h') {
 | 
							break
 | 
				
			||||||
 | 
						case 'h':
 | 
				
			||||||
		bufferToggleLog()
 | 
							bufferToggleLog()
 | 
				
			||||||
	} else if (event.altKey && event.key == 'a') {
 | 
							break
 | 
				
			||||||
 | 
						case 'a':
 | 
				
			||||||
		for (const [name, b] of buffers)
 | 
							for (const [name, b] of buffers)
 | 
				
			||||||
			if (name !== bufferCurrent && b.newMessages) {
 | 
								if (name !== bufferCurrent && b.newMessages) {
 | 
				
			||||||
				bufferActivate(name)
 | 
									bufferActivate(name)
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
	} else if (event.altKey && event.key == '!') {
 | 
							break
 | 
				
			||||||
 | 
						case '!':
 | 
				
			||||||
		for (const [name, b] of buffers)
 | 
							for (const [name, b] of buffers)
 | 
				
			||||||
			if (name !== bufferCurrent && b.highlighted) {
 | 
								if (name !== bufferCurrent && b.highlighted) {
 | 
				
			||||||
				bufferActivate(name)
 | 
									bufferActivate(name)
 | 
				
			||||||
				break
 | 
									break
 | 
				
			||||||
			}
 | 
								}
 | 
				
			||||||
	} else
 | 
							break
 | 
				
			||||||
 | 
						default:
 | 
				
			||||||
		return
 | 
							return
 | 
				
			||||||
 | 
						}
 | 
				
			||||||
 | 
					
 | 
				
			||||||
	event.preventDefault()
 | 
						event.preventDefault()
 | 
				
			||||||
})
 | 
					})
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user