From 00eebc9859be03fb974449c785398e706cf6a14c Mon Sep 17 00:00:00 2001 From: Simon Ser Date: Wed, 30 Jun 2021 22:20:40 +0200 Subject: [PATCH] composer: cycle through auto-completions Closes: https://todo.sr.ht/~emersion/gamja/42 --- components/app.js | 14 +++----- components/composer.js | 79 +++++++++++++++++++++++++++++------------- 2 files changed, 58 insertions(+), 35 deletions(-) diff --git a/components/app.js b/components/app.js index 78e7550..b27a671 100644 --- a/components/app.js +++ b/components/app.js @@ -1007,13 +1007,10 @@ export default class App extends Component { autocomplete(prefix) { function fromList(l, prefix) { prefix = prefix.toLowerCase(); - let repl = null; + let repl = []; for (let item of l) { if (item.toLowerCase().startsWith(prefix)) { - if (repl) { - return null; - } - repl = item; + repl.push(item); } } return repl; @@ -1021,15 +1018,12 @@ export default class App extends Component { if (prefix.startsWith("/")) { let repl = fromList(Object.keys(commands), prefix.slice(1)); - if (repl) { - repl = "/" + repl; - } - return repl; + return repl.map(cmd => "/" + cmd); } let buf = this.state.buffers.get(this.state.activeBuffer); if (!buf || !buf.members) { - return null; + return []; } return fromList(buf.members.keys(), prefix); } diff --git a/components/composer.js b/components/composer.js index cd62797..d33720a 100644 --- a/components/composer.js +++ b/components/composer.js @@ -5,6 +5,7 @@ export default class Composer extends Component { text: "", }; textInput = createRef(); + lastAutocomplete = null; constructor(props) { super(props); @@ -42,48 +43,76 @@ export default class Composer extends Component { event.preventDefault(); - let carretIndex = input.selectionStart; + let carretPos = input.selectionStart; let text = this.state.text; - let wordStart; - for (wordStart = carretIndex - 1; wordStart >= 0; wordStart--) { - if (text[wordStart] === " ") { - break; + let autocomplete; + if (this.lastAutocomplete && this.lastAutocomplete.text === text && this.lastAutocomplete.carretPos === carretPos) { + autocomplete = this.lastAutocomplete; + } else { + this.lastAutocomplete = null; + + let wordStart; + for (wordStart = carretPos - 1; wordStart >= 0; wordStart--) { + if (text[wordStart] === " ") { + break; + } } - } - wordStart++; + wordStart++; - let wordEnd; - for (wordEnd = carretIndex; wordEnd < text.length; wordEnd++) { - if (text[wordEnd] === " ") { - break; + let wordEnd; + for (wordEnd = carretPos; wordEnd < text.length; wordEnd++) { + if (text[wordEnd] === " ") { + break; + } } + + let word = text.slice(wordStart, wordEnd); + if (!word) { + return; + } + + let replacements = this.props.autocomplete(word); + if (replacements.length === 0) { + return; + } + + autocomplete = { + text, + carretPos: input.selectionStart, + prefix: text.slice(0, wordStart), + suffix: text.slice(wordEnd), + replacements, + replIndex: -1, + }; } - let word = text.slice(wordStart, wordEnd); - if (!word) { - return; + let n = autocomplete.replacements.length; + if (event.shiftKey) { + autocomplete.replIndex--; + } else { + autocomplete.replIndex++; } + autocomplete.replIndex = (autocomplete.replIndex + n) % n; - let repl = this.props.autocomplete(word); - if (!repl) { - return; - } - - if (wordStart === 0 && wordEnd === text.length) { - if (word.startsWith("/")) { + let repl = autocomplete.replacements[autocomplete.replIndex]; + if (!autocomplete.prefix && !autocomplete.suffix) { + if (repl.startsWith("/")) { repl += " "; } else { repl += ": "; } } - text = text.slice(0, wordStart) + repl + text.slice(wordEnd); + autocomplete.text = autocomplete.prefix + repl + autocomplete.suffix; + autocomplete.carretPos = autocomplete.prefix.length + repl.length; - input.value = text; - input.selectionStart = wordStart + repl.length; + input.value = autocomplete.text; + input.selectionStart = autocomplete.carretPos; input.selectionEnd = input.selectionStart; - this.setState({ text }); + this.lastAutocomplete = autocomplete; + + this.setState({ text: autocomplete.text }); } handleWindowKeyDown(event) {