autocomplete polishing

* suppress autocomplete when navigating through history
* only search for slashcommands if in the first block of the editor
* handle suffix returns from providers correctly
* fix bugs when pressing ctrl-a, typing and then tab to complete a replacement by collapsing selection to anchor when inserting a completion in the editor
This commit is contained in:
Matthew Hodgson 2018-05-13 03:16:55 +01:00
parent c967ecc4e5
commit 5605439e76
2 changed files with 23 additions and 8 deletions

View file

@ -114,7 +114,7 @@ export default class Autocomplete extends React.Component {
processQuery(query, selection) { processQuery(query, selection) {
return this.autocompleter.getCompletions( return this.autocompleter.getCompletions(
query, selection, this.state.forceComplete, query, selection, this.state.forceComplete
).then((completions) => { ).then((completions) => {
// Only ever process the completions for the most recent query being processed // Only ever process the completions for the most recent query being processed
if (query !== this.queryRequested) { if (query !== this.queryRequested) {

View file

@ -178,6 +178,7 @@ export default class MessageComposerInput extends React.Component {
this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' }); this.plainWithIdPills = new PlainWithPillsSerializer({ pillFormat: 'id' });
this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' }); this.plainWithPlainPills = new PlainWithPillsSerializer({ pillFormat: 'plain' });
this.suppressAutoComplete = false;
this.direction = ''; this.direction = '';
} }
@ -535,6 +536,8 @@ export default class MessageComposerInput extends React.Component {
onKeyDown = (ev: Event, change: Change, editor: Editor) => { onKeyDown = (ev: Event, change: Change, editor: Editor) => {
this.suppressAutoComplete = false;
// skip void nodes - see // skip void nodes - see
// https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095 // https://github.com/ianstormtaylor/slate/issues/762#issuecomment-304855095
if (ev.keyCode === KeyCode.LEFT) { if (ev.keyCode === KeyCode.LEFT) {
@ -978,6 +981,8 @@ export default class MessageComposerInput extends React.Component {
// we skip it for now given we know we're about to setState anyway // we skip it for now given we know we're about to setState anyway
editorState = change.value; editorState = change.value;
this.suppressAutoComplete = true;
this.setState({ editorState }, ()=>{ this.setState({ editorState }, ()=>{
this.refs.editor.focus(); this.refs.editor.focus();
}); });
@ -1061,13 +1066,15 @@ export default class MessageComposerInput extends React.Component {
let editorState = activeEditorState; let editorState = activeEditorState;
if (range) { if (range) {
const change = editorState.change().moveOffsetsTo(range.start, range.end); const change = editorState.change()
.collapseToAnchor()
.moveOffsetsTo(range.start, range.end);
editorState = change.value; editorState = change.value;
} }
const change = editorState.change().insertInlineAtRange( const change = editorState.change()
editorState.selection, inline .insertInlineAtRange(editorState.selection, inline)
); .insertText(suffix);
editorState = change.value; editorState = change.value;
this.setState({ editorState, originalEditorState: activeEditorState }, ()=>{ this.setState({ editorState, originalEditorState: activeEditorState }, ()=>{
@ -1185,13 +1192,12 @@ export default class MessageComposerInput extends React.Component {
} }
getAutocompleteQuery(editorState: Value) { getAutocompleteQuery(editorState: Value) {
// FIXME: do we really want to regenerate this every time the control is rerendered?
// We can just return the current block where the selection begins, which // We can just return the current block where the selection begins, which
// should be enough to capture any autocompletion input, given autocompletion // should be enough to capture any autocompletion input, given autocompletion
// providers only search for the first match which intersects with the current selection. // providers only search for the first match which intersects with the current selection.
// This avoids us having to serialize the whole thing to plaintext and convert // This avoids us having to serialize the whole thing to plaintext and convert
// selection offsets in & out of the plaintext domain. // selection offsets in & out of the plaintext domain.
return editorState.document.getDescendant(editorState.selection.anchorKey).text; return editorState.document.getDescendant(editorState.selection.anchorKey).text;
// Don't send markdown links to the autocompleter // Don't send markdown links to the autocompleter
@ -1199,10 +1205,19 @@ export default class MessageComposerInput extends React.Component {
} }
getSelectionRange(editorState: Value) { getSelectionRange(editorState: Value) {
let beginning = false;
const query = this.getAutocompleteQuery(editorState);
const firstChild = editorState.document.nodes.get(0);
const firstGrandChild = firstChild && firstChild.nodes.get(0);
beginning = (firstChild && firstGrandChild &&
firstChild.object === 'block' && firstGrandChild.object === 'text' &&
editorState.selection.anchorKey === firstGrandChild.key);
// return a character range suitable for handing to an autocomplete provider. // return a character range suitable for handing to an autocomplete provider.
// the range is relative to the anchor of the current editor selection. // the range is relative to the anchor of the current editor selection.
// if the selection spans multiple blocks, then we collapse it for the calculation. // if the selection spans multiple blocks, then we collapse it for the calculation.
const range = { const range = {
beginning, // whether the selection is in the first block of the editor or not
start: editorState.selection.anchorOffset, start: editorState.selection.anchorOffset,
end: (editorState.selection.anchorKey == editorState.selection.focusKey) ? end: (editorState.selection.anchorKey == editorState.selection.focusKey) ?
editorState.selection.focusOffset : editorState.selection.anchorOffset, editorState.selection.focusOffset : editorState.selection.anchorOffset,
@ -1273,7 +1288,7 @@ export default class MessageComposerInput extends React.Component {
room={this.props.room} room={this.props.room}
onConfirm={this.setDisplayedCompletion} onConfirm={this.setDisplayedCompletion}
onSelectionChange={this.setDisplayedCompletion} onSelectionChange={this.setDisplayedCompletion}
query={this.getAutocompleteQuery(activeEditorState)} query={ this.suppressAutoComplete ? '' : this.getAutocompleteQuery(activeEditorState) }
selection={this.getSelectionRange(activeEditorState)} selection={this.getSelectionRange(activeEditorState)}
/> />
</div> </div>