composer: cycle through auto-completions

Closes: https://todo.sr.ht/~emersion/gamja/42
This commit is contained in:
Simon Ser 2021-06-30 22:20:40 +02:00
parent 08aefc9dc5
commit 00eebc9859
2 changed files with 58 additions and 35 deletions

View File

@ -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);
}

View File

@ -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) {