diff --git a/xP/public/xP.css b/xP/public/xP.css index c231620..96d6525 100644 --- a/xP/public/xP.css +++ b/xP/public/xP.css @@ -91,6 +91,7 @@ button { display: flex; flex-direction: column; overflow: hidden; + position: relative; } .filler { flex: auto; @@ -170,6 +171,22 @@ button { font-family: monospace; } +.completions { + position: absolute; + left: 0; + right: 0; + bottom: 0; + background: #fff; + padding: .05em .3em; + border-top: 1px solid #888; + + max-height: 50%; + display: flex; + flex-flow: column wrap; + column-gap: .6em; + overflow-x: auto; +} + textarea { font: inherit; padding: .05em .3em; diff --git a/xP/public/xP.js b/xP/public/xP.js index 1cdb8b0..dbba7e2 100644 --- a/xP/public/xP.js +++ b/xP/public/xP.js @@ -652,11 +652,28 @@ let Log = { }, } +let Completions = { + entries: [], + + reset: list => { + Completions.entries = list || [] + m.redraw() + }, + + view: vnode => { + if (!Completions.entries.length) + return + return m('.completions', {}, + Completions.entries.map(option => m('.completion', {}, option))) + }, +} + let BufferContainer = { view: vnode => { return m('.buffer-container', {}, [ m('.filler'), bufferLog !== undefined ? m(Log) : m(Buffer), + m(Completions), ]) }, } @@ -711,17 +728,20 @@ let Input = { let preceding = utf8Encode(textarea.value).slice(0, resp.start) let start = utf8Decode(preceding).length - - // TODO: Somehow display remaining options, or cycle through. - if (resp.completions.length) { + if (resp.completions.length > 0) { textarea.setRangeText(resp.completions[0], start, textarea.selectionEnd, 'end') + } + + if (resp.completions.length == 1) { + textarea.setRangeText(' ', + textarea.selectionStart, textarea.selectionEnd, 'end') } else { beep() } - if (resp.completions.length === 1) - textarea.setRangeText(' ', - textarea.selectionStart, textarea.selectionEnd, 'end') + + if (resp.completions.length > 1) + Completions.reset(resp.completions.slice(1)) }) return true }, @@ -886,7 +906,15 @@ let Input = { }, view: vnode => { - return m('textarea#input', {rows: 1, onkeydown: Input.onKeyDown}) + return m('textarea#input', { + rows: 1, + onkeydown: Input.onKeyDown, + oninput: event => Completions.reset(), + // Sadly only supported in Firefox as of writing. + onselectionchange: event => Completions.reset(), + // The list of completions is scrollable without receiving focus. + onblur: event => Completions.reset(), + }) }, }